88_HMCCU.pm 206 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548
  1. ##############################################################################
  2. #
  3. # 88_HMCCU.pm
  4. #
  5. # $Id: 88_HMCCU.pm 15429 2017-11-13 15:36:46Z zap $
  6. #
  7. # Version 4.1.004
  8. #
  9. # Module for communication between FHEM and Homematic CCU2.
  10. #
  11. # Supports BidCos-RF, BidCos-Wired, HmIP-RF, virtual CCU channels,
  12. # CCU group devices, HomeGear, CUxD, Osram Lightify.
  13. #
  14. # (c) 2017 by zap (zap01 <at> t-online <dot> de)
  15. #
  16. ##############################################################################
  17. #
  18. # define <name> HMCCU <hostname_or_ip_of_ccu> [ccunumber] [waitforccu=<seconds>]
  19. #
  20. # set <name> cleardefaults
  21. # set <name> defaults
  22. # set <name> delete <name> [{ OT_VARDP | OT_DEVICE }]
  23. # set <name> execute <ccu_program>
  24. # set <name> importdefaults <filename>
  25. # set <name> hmscript {<scriptfile>|!<function>|'['<code>']'} [dump] [<parname>=<value> [...]]
  26. # set <name> rpcserver {on|off|restart}
  27. # set <name> var [<type>] <name> <value> [<parameter>=<value> [...]]
  28. #
  29. # get <name> aggregation {<rule>|all}
  30. # get <name> configdesc {<device>|<channel>}
  31. # get <name> defaults
  32. # get <name> deviceinfo <device>
  33. # get <name> devicelist [dump]
  34. # get <name> devicelist create <devexp> [t={chn|dev|all}] [s=<suffix>] [p=<prefix>] [f=<format>]
  35. # [defattr] [duplicates] [save] [<attr>=<val> [...]]}]
  36. # get <name> dump {devtypes|datapoints} [<filter>]
  37. # get <name> dutycycle
  38. # get <name> exportdefaults {filename}
  39. # get <name> firmware
  40. # get <name> parfile [<parfile>]
  41. # get <name> rpcevents
  42. # get <name> rpcstate
  43. # get <name> update [<fhemdevexp> [{ State | Value }]]
  44. # get <name> updateccu [<devexp> [{ State | Value }]]
  45. # get <name> vars <regexp>
  46. #
  47. # attr <name> ccuackstate { 0 | 1 }
  48. # attr <name> ccuaggregate <rules>
  49. # attr <name> ccudef-hmstatevals <subst_rules>
  50. # attr <name> ccudef-readingfilter <filter_rule>
  51. # attr <name> ccudef-readingname <rules>
  52. # attr <name> ccudef-substitute <subst_rule>
  53. # attr <name> ccudefaults <filename>
  54. # attr <name> ccuflags { intrpc,extrpc,dptnocheck,noagg }
  55. # attr <name> ccuget { State | Value }
  56. # attr <name> ccureadings { 0 | 1 }
  57. # attr <name> parfile <parfile>
  58. # attr <name> rpcevtimeout <seconds>
  59. # attr <name> rpcinterfaces { BidCos-Wired, BidCos-RF, HmIP-RF, VirtualDevices, Homegear }
  60. # attr <name> rpcinterval <seconds>
  61. # attr <name> rpcport <ccu_rpc_port>
  62. # attr <name> rpcqueue <file>
  63. # attr <name> rpcserver { on | off }
  64. # attr <name> rpcserveraddr <ip-or-name>
  65. # attr <name> rpcserverport <base_port>
  66. # attr <name> rpctimeout <read>[,<write>]
  67. # attr <name> stripchar <character>
  68. # attr <name> stripnumber [<datapoint-expr!]{-<digits>|0|1|2}[;...]
  69. # attr <name> substitute <subst_rule>
  70. #
  71. # filter_rule := channel-regexp!datapoint-regexp[;...]
  72. # subst_rule := [[channel.]datapoint[,...]!]<regexp>:<subtext>[,...][;...]
  73. ##############################################################################
  74. # Verbose levels:
  75. #
  76. # 0 = Log start/stop and initialization messages
  77. # 1 = Log errors
  78. # 2 = Log counters and warnings
  79. # 3 = Log events and runtime information
  80. ##############################################################################
  81. package main;
  82. no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  83. use strict;
  84. use warnings;
  85. # use Data::Dumper;
  86. # use Time::HiRes qw(usleep);
  87. use IO::File;
  88. use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR';
  89. use RPC::XML::Client;
  90. use RPC::XML::Server;
  91. use SetExtensions;
  92. use SubProcess;
  93. use HMCCUConf;
  94. # Import configuration data
  95. my $HMCCU_CHN_DEFAULTS = \%HMCCUConf::HMCCU_CHN_DEFAULTS;
  96. my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS;
  97. my $HMCCU_SCRIPTS = \%HMCCUConf::HMCCU_SCRIPTS;
  98. # Custom configuration data
  99. my %HMCCU_CUST_CHN_DEFAULTS;
  100. my %HMCCU_CUST_DEV_DEFAULTS;
  101. # HMCCU version
  102. my $HMCCU_VERSION = '4.1.004';
  103. # Default RPC port (BidCos-RF)
  104. my $HMCCU_RPC_PORT_DEFAULT = 2001;
  105. # RPC port name by port number
  106. my %HMCCU_RPC_NUMPORT = (
  107. 2000 => 'BidCos-Wired', 2001 => 'BidCos-RF', 2010 => 'HmIP-RF', 9292 => 'VirtualDevices',
  108. 2003 => 'Homegear', 8701 => 'CUxD'
  109. );
  110. # RPC port number by port name
  111. my %HMCCU_RPC_PORT = (
  112. 'BidCos-Wired', 2000, 'BidCos-RF', 2001, 'HmIP-RF', 2010, 'VirtualDevices', 9292,
  113. 'Homegear', 2003, 'CUxD', 8701
  114. );
  115. # RPC URL extensions by port number
  116. my %HMCCU_RPC_URL = (
  117. 9292, 'groups'
  118. );
  119. # RPC protocol types by port name. A=ASCII, B=Binary
  120. my %HMCCU_RPC_PROT = (
  121. 2000 => 'A', 2001 => 'A', 2010 => 'A', 9292 => 'A', 2003 => 'A', 8701 => 'B'
  122. );
  123. # Initial intervals for registration of RPC callbacks and reading RPC queue
  124. #
  125. # X = Start RPC server
  126. # X+HMCCU_INIT_INTERVAL1 = Register RPC callback
  127. # X+HMCCU_INIT_INTERVAL2 = Read RPC Queue
  128. #
  129. my $HMCCU_INIT_INTERVAL0 = 12;
  130. my $HMCCU_INIT_INTERVAL1 = 7;
  131. my $HMCCU_INIT_INTERVAL2 = 5;
  132. # Number of arguments in RPC events
  133. my %rpceventargs = (
  134. "EV", 3,
  135. "ND", 6,
  136. "DD", 1,
  137. "RD", 2,
  138. "RA", 1,
  139. "UD", 2,
  140. "IN", 3,
  141. "EX", 3,
  142. "SL", 2,
  143. "ST", 10
  144. );
  145. # Datapoint operations
  146. my $HMCCU_OPER_READ = 1;
  147. my $HMCCU_OPER_WRITE = 2;
  148. my $HMCCU_OPER_EVENT = 4;
  149. # Datapoint types
  150. my $HMCCU_TYPE_BINARY = 2;
  151. my $HMCCU_TYPE_FLOAT = 4;
  152. my $HMCCU_TYPE_INTEGER = 16;
  153. my $HMCCU_TYPE_STRING = 20;
  154. # Flags for CCU object specification
  155. my $HMCCU_FLAG_NAME = 1;
  156. my $HMCCU_FLAG_CHANNEL = 2;
  157. my $HMCCU_FLAG_DATAPOINT = 4;
  158. my $HMCCU_FLAG_ADDRESS = 8;
  159. my $HMCCU_FLAG_INTERFACE = 16;
  160. my $HMCCU_FLAG_FULLADDR = 32;
  161. # Valid flag combinations
  162. my $HMCCU_FLAGS_IACD = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS |
  163. $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  164. my $HMCCU_FLAGS_IAC = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL;
  165. my $HMCCU_FLAGS_ACD = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  166. my $HMCCU_FLAGS_AC = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL;
  167. my $HMCCU_FLAGS_ND = $HMCCU_FLAG_NAME | $HMCCU_FLAG_DATAPOINT;
  168. my $HMCCU_FLAGS_NC = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL;
  169. my $HMCCU_FLAGS_NCD = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  170. # Default values
  171. my $HMCCU_DEF_HMSTATE = '^0\.UNREACH!(1|true):unreachable;^[0-9]\.LOW_?BAT!(1|true):warn_battery';
  172. # Binary RPC data types
  173. my $BINRPC_INTEGER = 1;
  174. my $BINRPC_BOOL = 2;
  175. my $BINRPC_STRING = 3;
  176. my $BINRPC_DOUBLE = 4;
  177. my $BINRPC_BASE64 = 17;
  178. my $BINRPC_ARRAY = 256;
  179. my $BINRPC_STRUCT = 257;
  180. # Declare functions
  181. sub HMCCU_Initialize ($);
  182. sub HMCCU_Define ($$);
  183. sub HMCCU_Undef ($$);
  184. sub HMCCU_Shutdown ($);
  185. sub HMCCU_Set ($@);
  186. sub HMCCU_Get ($@);
  187. sub HMCCU_Attr ($@);
  188. sub HMCCU_AggregationRules ($$);
  189. sub HMCCU_ExportDefaults ($);
  190. sub HMCCU_ImportDefaults ($);
  191. sub HMCCU_FindDefaults ($$);
  192. sub HMCCU_SetDefaults ($);
  193. sub HMCCU_GetDefaults ($$);
  194. sub HMCCU_Notify ($$);
  195. sub HMCCU_AggregateReadings ($$);
  196. sub HMCCU_ParseObject ($$$);
  197. sub HMCCU_FilterReading ($$$);
  198. sub HMCCU_GetReadingName ($$$$$$$);
  199. sub HMCCU_FormatReadingValue ($$$);
  200. sub HMCCU_Trace ($$$$);
  201. sub HMCCU_Log ($$$$);
  202. sub HMCCU_SetError ($@);
  203. sub HMCCU_SetState ($@);
  204. sub HMCCU_Substitute ($$$$$);
  205. sub HMCCU_SubstRule ($$$);
  206. sub HMCCU_SubstVariables ($$$);
  207. sub HMCCU_UpdateClients ($$$$);
  208. sub HMCCU_UpdateDeviceTable ($$);
  209. sub HMCCU_UpdateSingleDatapoint ($$$$);
  210. sub HMCCU_UpdateSingleDevice ($$$);
  211. sub HMCCU_UpdateMultipleDevices ($$);
  212. sub HMCCU_UpdatePeers ($$$$);
  213. sub HMCCU_GetRPCPortList ($);
  214. sub HMCCU_RPCRegisterCallback ($);
  215. sub HMCCU_RPCDeRegisterCallback ($);
  216. sub HMCCU_ResetCounters ($);
  217. sub HMCCU_StartExtRPCServer ($);
  218. sub HMCCU_StopExtRPCServer ($);
  219. sub HMCCU_StartIntRPCServer ($);
  220. sub HMCCU_StopRPCServer ($);
  221. sub HMCCU_IsRPCStateBlocking ($);
  222. sub HMCCU_IsRPCServerRunning ($$$);
  223. sub HMCCU_GetDeviceInfo ($$$);
  224. sub HMCCU_FormatDeviceInfo ($);
  225. sub HMCCU_GetFirmwareVersions ($);
  226. sub HMCCU_GetDeviceList ($);
  227. sub HMCCU_GetDatapointList ($);
  228. sub HMCCU_FindDatapoint ($$$$$);
  229. sub HMCCU_GetAddress ($$$$);
  230. sub HMCCU_IsDevAddr ($$);
  231. sub HMCCU_IsChnAddr ($$);
  232. sub HMCCU_SplitChnAddr ($);
  233. sub HMCCU_GetCCUObjectAttribute ($$$);
  234. sub HMCCU_FindClientDevices ($$$$);
  235. sub HMCCU_GetRPCDevice ($$);
  236. sub HMCCU_FindIODevice ($);
  237. sub HMCCU_GetHash ($@);
  238. sub HMCCU_GetAttribute ($$$$);
  239. sub HMCCU_GetDatapointCount ($$$);
  240. sub HMCCU_GetSpecialDatapoints ($$$$$);
  241. sub HMCCU_GetAttrReadingFormat ($$);
  242. sub HMCCU_GetAttrSubstitute ($$);
  243. sub HMCCU_IsValidDeviceOrChannel ($$);
  244. sub HMCCU_IsValidDevice ($$);
  245. sub HMCCU_IsValidChannel ($$);
  246. sub HMCCU_GetCCUDeviceParam ($$);
  247. sub HMCCU_GetValidDatapoints ($$$$$);
  248. sub HMCCU_GetSwitchDatapoint ($$$);
  249. sub HMCCU_IsValidDatapoint ($$$$$);
  250. sub HMCCU_GetMatchingDevices ($$$$);
  251. sub HMCCU_GetDeviceName ($$$);
  252. sub HMCCU_GetChannelName ($$$);
  253. sub HMCCU_GetDeviceType ($$$);
  254. sub HMCCU_GetDeviceChannels ($$$);
  255. sub HMCCU_GetDeviceInterface ($$$);
  256. sub HMCCU_ResetRPCQueue ($$);
  257. sub HMCCU_ReadRPCQueue ($);
  258. sub HMCCU_ProcessEvent ($$);
  259. sub HMCCU_HMScriptExt ($$$);
  260. sub HMCCU_BulkUpdate ($$$$);
  261. sub HMCCU_GetDatapoint ($@);
  262. sub HMCCU_SetDatapoint ($$$);
  263. sub HMCCU_ScaleValue ($$$$);
  264. sub HMCCU_GetVariables ($$);
  265. sub HMCCU_SetVariable ($$$$$);
  266. sub HMCCU_GetUpdate ($$$);
  267. sub HMCCU_GetChannel ($$);
  268. sub HMCCU_RPCGetConfig ($$$$);
  269. sub HMCCU_RPCSetConfig ($$$);
  270. # File queue functions
  271. sub HMCCU_QueueOpen ($$);
  272. sub HMCCU_QueueClose ($);
  273. sub HMCCU_QueueReset ($);
  274. sub HMCCU_QueueEnq ($$);
  275. sub HMCCU_QueueDeq ($);
  276. # Helper functions
  277. sub HMCCU_GetHMState ($$$);
  278. sub HMCCU_GetTimeSpec ($);
  279. sub HMCCU_CalculateReading ($$$);
  280. sub HMCCU_EncodeEPDisplay ($);
  281. sub HMCCU_RefToString ($);
  282. sub HMCCU_ExprMatch ($$$);
  283. sub HMCCU_ExprNotMatch ($$$);
  284. sub HMCCU_GetDutyCycle ($);
  285. sub HMCCU_TCPPing ($$$);
  286. sub HMCCU_TCPConnect ($$);
  287. sub HMCCU_CorrectName ($);
  288. # Subprocess functions
  289. sub HMCCU_CCURPC_Write ($$);
  290. sub HMCCU_CCURPC_OnRun ($);
  291. sub HMCCU_CCURPC_OnExit ();
  292. sub HMCCU_CCURPC_NewDevicesCB ($$$);
  293. sub HMCCU_CCURPC_DeleteDevicesCB ($$$);
  294. sub HMCCU_CCURPC_UpdateDeviceCB ($$$$);
  295. sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$);
  296. sub HMCCU_CCURPC_ReaddDevicesCB ($$$);
  297. sub HMCCU_CCURPC_EventCB ($$$$$);
  298. sub HMCCU_CCURPC_ListDevicesCB ($$);
  299. ##################################################
  300. # Initialize module
  301. ##################################################
  302. sub HMCCU_Initialize ($)
  303. {
  304. my ($hash) = @_;
  305. $hash->{DefFn} = "HMCCU_Define";
  306. $hash->{UndefFn} = "HMCCU_Undef";
  307. $hash->{SetFn} = "HMCCU_Set";
  308. $hash->{GetFn} = "HMCCU_Get";
  309. $hash->{ReadFn} = "HMCCU_Read";
  310. $hash->{AttrFn} = "HMCCU_Attr";
  311. $hash->{NotifyFn} = "HMCCU_Notify";
  312. $hash->{ShutdownFn} = "HMCCU_Shutdown";
  313. $hash->{parseParams} = 1;
  314. $hash->{AttrList} = "stripchar stripnumber ccuackstate:0,1 ccuaggregate:textField-long".
  315. " ccudefaults rpcinterfaces:multiple-strict,".join(',',sort keys %HMCCU_RPC_PORT).
  316. " ccudef-hmstatevals:textField-long ccudef-substitute:textField-long".
  317. " ccudef-readingname:textField-long ccudef-readingfilter:textField-long".
  318. " ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc".
  319. " ccuflags:multiple-strict,extrpc,intrpc,dptnocheck,noagg,nohmstate ccureadings:0,1".
  320. " rpcdevice rpcinterval:2,3,5,7,10 rpcqueue".
  321. " rpcport:multiple-strict,".join(',',sort keys %HMCCU_RPC_NUMPORT).
  322. " rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout parfile substitute".
  323. " ccuget:Value,State ".
  324. $readingFnAttributes;
  325. }
  326. ######################################################################
  327. # Define device
  328. ######################################################################
  329. sub HMCCU_Define ($$)
  330. {
  331. my ($hash, $a, $h) = @_;
  332. my $name = $hash->{NAME};
  333. return "Specify CCU hostname or IP address as a parameter" if(scalar (@$a) < 3);
  334. $hash->{host} = $$a[2];
  335. $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPC:';
  336. # Check if TCL-Rega process is running on CCU
  337. my $timeout = exists ($h->{waitforccu}) ? $h->{waitforccu} : 0;
  338. if (HMCCU_TCPPing ($hash->{host}, 8181, $timeout)) {
  339. $hash->{ccustate} = 'active';
  340. }
  341. else {
  342. $hash->{ccustate} = 'unreachable';
  343. Log3 $name, 1, "HMCCU: CCU2 is not reachable";
  344. }
  345. if (scalar (@$a) >= 4) {
  346. return "CCU number must be in range 1-9" if ($$a[3] < 1 || $$a[3] > 9);
  347. $hash->{CCUNum} = $$a[3];
  348. }
  349. else {
  350. # Count CCU devices
  351. my $ccucount = 0;
  352. foreach my $d (keys %defs) {
  353. my $ch = $defs{$d};
  354. next if (!exists ($ch->{TYPE}));
  355. $ccucount++ if ($ch->{TYPE} eq 'HMCCU' && $ch != $hash);
  356. $ccucount++ if ($ch->{TYPE} eq 'HMCCURPC' && $ch->{noiodev} == 1);
  357. }
  358. $hash->{CCUNum} = $ccucount+1;
  359. }
  360. $hash->{version} = $HMCCU_VERSION;
  361. $hash->{ccutype} = 'CCU2';
  362. $hash->{RPCState} = "stopped";
  363. Log3 $name, 1, "HMCCU: Device $name. Initialized version $HMCCU_VERSION";
  364. my ($devcnt, $chncnt) = HMCCU_GetDeviceList ($hash);
  365. if ($devcnt > 0) {
  366. Log3 $name, 1, "HMCCU: Read $devcnt devices with $chncnt channels from CCU ".$hash->{host};
  367. }
  368. else {
  369. Log3 $name, 1, "HMCCU: No devices read from CCU ".$hash->{host};
  370. }
  371. $hash->{hmccu}{evtime} = 0;
  372. $hash->{hmccu}{evtimeout} = 0;
  373. $hash->{hmccu}{updatetime} = 0;
  374. $hash->{hmccu}{rpccount} = 0;
  375. $hash->{hmccu}{rpcports} = $HMCCU_RPC_PORT_DEFAULT;
  376. readingsBeginUpdate ($hash);
  377. readingsBulkUpdate ($hash, "state", "Initialized");
  378. readingsBulkUpdate ($hash, "rpcstate", "stopped");
  379. readingsEndUpdate ($hash, 1);
  380. $attr{$name}{stateFormat} = "rpcstate/state";
  381. return undef;
  382. }
  383. ######################################################################
  384. # Set or delete attribute
  385. ######################################################################
  386. sub HMCCU_Attr ($@)
  387. {
  388. my ($cmd, $name, $attrname, $attrval) = @_;
  389. my $hash = $defs{$name};
  390. my $rc = 0;
  391. if ($cmd eq 'set') {
  392. if ($attrname eq 'ccudefaults') {
  393. $rc = HMCCU_ImportDefaults ($attrval);
  394. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  395. if ($rc < 0) {
  396. $rc = -$rc;
  397. return HMCCU_SetError ($hash,
  398. "Syntax error in default attribute file $attrval line $rc");
  399. }
  400. }
  401. elsif ($attrname eq 'ccuaggregate') {
  402. $rc = HMCCU_AggregationRules ($hash, $attrval);
  403. return HMCCU_SetError ($hash, "Syntax error in attribute ccuaggregate") if ($rc == 0);
  404. }
  405. elsif ($attrname eq 'ccuflags') {
  406. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  407. if ($attrval =~ /extrpc/ && $attrval =~ /intrpc/) {
  408. return "Flags extrpc and inttpc cannot be combined";
  409. }
  410. if ($attrval =~ /(extrpc|intrpc)/) {
  411. my $rpcmode = $1;
  412. if ($ccuflags !~ /$rpcmode/) {
  413. return "Stop RPC server before switching between extrpc and intrpc"
  414. if (HMCCU_IsRPCServerRunning ($hash, undef, undef));
  415. }
  416. }
  417. }
  418. elsif ($attrname eq 'rpcdevice') {
  419. return "HMCCU: Can't find HMCCURPC device $attrval"
  420. if (!exists ($defs{$attrval}) || $defs{$attrval}->{TYPE} ne 'HMCCURPC');
  421. if (exists ($defs{$attrval}->{IODev})) {
  422. return "HMCCU: Device $attrval is not assigned to $name"
  423. if ($defs{$attrval}->{IODev} != $hash);
  424. }
  425. else {
  426. $defs{$attrval}->{IODev} = $hash;
  427. }
  428. $hash->{RPCDEV} = $attrval;
  429. }
  430. elsif ($attrname eq 'rpcinterfaces') {
  431. my @ports = split (',', $attrval);
  432. my @plist = ();
  433. foreach my $p (@ports) {
  434. return "Illegal RPC interface $p" if (!exists ($HMCCU_RPC_PORT{$p}));
  435. push (@plist, $HMCCU_RPC_PORT{$p});
  436. }
  437. return "No RPC interface specified" if (scalar (@plist) == 0);
  438. $hash->{hmccu}{rpcports} = join (',', @plist);
  439. $attr{$name}{"rpcport"} = $hash->{hmccu}{rpcports};
  440. }
  441. elsif ($attrname eq 'rpcport') {
  442. my @ports = split (',', $attrval);
  443. my @ilist = ();
  444. foreach my $p (@ports) {
  445. return "Illegal RPC port $p" if (!exists ($HMCCU_RPC_NUMPORT{$p}));
  446. push (@ilist, $HMCCU_RPC_NUMPORT{$p});
  447. }
  448. return "No RPC port specified" if (scalar (@ilist) == 0);
  449. $hash->{hmccu}{rpcports} = $attrval;
  450. $attr{$name}{"rpcinterfaces"} = join (',', @ilist);
  451. }
  452. }
  453. elsif ($cmd eq 'del') {
  454. if ($attrname eq 'ccuaggregate') {
  455. HMCCU_AggregationRules ($hash, '');
  456. }
  457. elsif ($attrname eq 'rpcdevice') {
  458. delete $hash->{RPCDEV} if (exists ($hash->{RPCDEV}));
  459. }
  460. elsif ($attrname eq 'rpcport' || $attrname eq 'rpcinterfaces') {
  461. $hash->{hmccu}{rpcports} = $HMCCU_RPC_PORT_DEFAULT;
  462. }
  463. }
  464. return undef;
  465. }
  466. ######################################################################
  467. # Parse aggregation rules for readings.
  468. # Syntax of aggregation rule is:
  469. # FilterSpec[;...]
  470. # FilterSpec := {Name|Filt|Read|Cond|Else|Pref|Coll|Html}[,...]
  471. # Name := name:Name
  472. # Filt := filter:{name|type|group|room|alias}=Regexp[!Regexp]
  473. # Read := read:Regexp
  474. # Cond := if:{any|all|min|max|sum|avg|gt|lt|ge|le}=Value
  475. # Else := else:Value
  476. # Pref := prefix:{RULE|Prefix}
  477. # Coll := coll:{NAME|Attribute}
  478. # Html := html:Template
  479. ######################################################################
  480. sub HMCCU_AggregationRules ($$)
  481. {
  482. my ($hash, $rulestr) = @_;
  483. my $name = $hash->{NAME};
  484. # Delete existing aggregation rules
  485. if (exists ($hash->{hmccu}{agg})) {
  486. delete $hash->{hmccu}{agg};
  487. }
  488. return if ($rulestr eq '');
  489. my @pars = ('name', 'filter', 'if', 'else');
  490. # Extract aggregation rules
  491. my $cnt = 0;
  492. my @rules = split (/[;\n]+/, $rulestr);
  493. foreach my $r (@rules) {
  494. $cnt++;
  495. # Set default rule parameters. Can be modified later
  496. my %opt = ( 'read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME' );
  497. # Parse aggregation rule
  498. my @specs = split (',', $r);
  499. foreach my $spec (@specs) {
  500. if ($spec =~ /^(name|filter|read|if|else|prefix|coll|html):(.+)$/) {
  501. $opt{$1} = $2;
  502. }
  503. }
  504. # Check if mandatory parameters are specified
  505. foreach my $p (@pars) {
  506. return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.", 0)
  507. if (!exists ($opt{$p}));
  508. }
  509. my $fname = $opt{name};
  510. my ($fincl, $fexcl) = split ('!', $opt{filter});
  511. my ($ftype, $fexpr) = split ('=', $fincl);
  512. return 0 if (!defined ($fexpr));
  513. my ($fcond, $fval) = split ('=', $opt{if});
  514. return 0 if (!defined ($fval));
  515. my ($fcoll, $fdflt) = split ('!', $opt{coll});
  516. $fdflt = 'no match' if (!defined ($fdflt));
  517. my $fhtml = exists ($opt{'html'}) ? $opt{'html'} : '';
  518. # Read HTML template (optional)
  519. if ($fhtml ne '') {
  520. my %tdef;
  521. my @html;
  522. # Read template file
  523. if (open (TEMPLATE, "<$fhtml")) {
  524. @html = <TEMPLATE>;
  525. close (TEMPLATE);
  526. }
  527. else {
  528. return HMCCU_Log ($hash, 1, "Can't open file $fhtml.", 0);
  529. }
  530. # Parse template
  531. foreach my $line (@html) {
  532. chomp $line;
  533. my ($key, $h) = split /:/, $line, 2;
  534. next if (!defined ($h) || $key =~ /^#/);
  535. $tdef{$key} = $h;
  536. }
  537. # Some syntax checks
  538. return HMCCU_Log ($hash, 1, "Missing definition row-odd in template file.", 0)
  539. if (!exists ($tdef{'row-odd'}));
  540. # Set default values
  541. $tdef{'begin-html'} = '' if (!exists ($tdef{'begin-html'}));
  542. $tdef{'end-html'} = '' if (!exists ($tdef{'end-html'}));
  543. $tdef{'begin-table'} = "<table>" if (!exists ($tdef{'begin-table'}));
  544. $tdef{'end-table'} = "</table>" if (!exists ($tdef{'end-table'}));
  545. $tdef{'default'} = 'no data' if (!exists ($tdef{'default'}));;
  546. $tdef{'row-even'} = $tdef{'row-odd'} if (!exists ($tdef{'row-even'}));
  547. foreach my $t (keys %tdef) {
  548. $hash->{hmccu}{agg}{$fname}{fhtml}{$t} = $tdef{$t};
  549. }
  550. }
  551. $hash->{hmccu}{agg}{$fname}{ftype} = $ftype;
  552. $hash->{hmccu}{agg}{$fname}{fexpr} = $fexpr;
  553. $hash->{hmccu}{agg}{$fname}{fexcl} = (defined ($fexcl) ? $fexcl : '');
  554. $hash->{hmccu}{agg}{$fname}{fread} = $opt{'read'};
  555. $hash->{hmccu}{agg}{$fname}{fcond} = $fcond;
  556. $hash->{hmccu}{agg}{$fname}{ftrue} = $fval;
  557. $hash->{hmccu}{agg}{$fname}{felse} = $opt{'else'};
  558. $hash->{hmccu}{agg}{$fname}{fpref} = $opt{prefix} eq 'RULE' ? $fname : $opt{prefix};
  559. $hash->{hmccu}{agg}{$fname}{fcoll} = $fcoll;
  560. $hash->{hmccu}{agg}{$fname}{fdflt} = $fdflt;
  561. }
  562. return 1;
  563. }
  564. ######################################################################
  565. # Export default attributes.
  566. ######################################################################
  567. sub HMCCU_ExportDefaults ($)
  568. {
  569. my ($filename) = @_;
  570. return 0 if (!open (DEFFILE, ">$filename"));
  571. print DEFFILE "# HMCCU default attributes for channels\n";
  572. foreach my $t (keys %{$HMCCU_CHN_DEFAULTS}) {
  573. print DEFFILE "\nchannel:$t\n";
  574. foreach my $a (sort keys %{$HMCCU_CHN_DEFAULTS->{$t}}) {
  575. print DEFFILE "$a=".$HMCCU_CHN_DEFAULTS->{$t}{$a}."\n";
  576. }
  577. }
  578. print DEFFILE "\n# HMCCU default attributes for devices\n";
  579. foreach my $t (keys %{$HMCCU_DEV_DEFAULTS}) {
  580. print DEFFILE "\ndevice:$t\n";
  581. foreach my $a (sort keys %{$HMCCU_DEV_DEFAULTS->{$t}}) {
  582. print DEFFILE "$a=".$HMCCU_DEV_DEFAULTS->{$t}{$a}."\n";
  583. }
  584. }
  585. close (DEFFILE);
  586. return 1;
  587. }
  588. ######################################################################
  589. # Import customer default attributes
  590. # Returns 1 on success. Returns negative line number on syntax errors.
  591. # Returns 0 on file open error.
  592. ######################################################################
  593. sub HMCCU_ImportDefaults ($)
  594. {
  595. my ($filename) = @_;
  596. my $modtype = '';
  597. my $ccutype = '';
  598. my $line = 0;
  599. return 0 if (!open (DEFFILE, "<$filename"));
  600. my @defaults = <DEFFILE>;
  601. close (DEFFILE);
  602. chomp (@defaults);
  603. %HMCCU_CUST_CHN_DEFAULTS = ();
  604. %HMCCU_CUST_DEV_DEFAULTS = ();
  605. foreach my $d (@defaults) {
  606. $line++;
  607. next if ($d eq '' || $d =~ /^#/);
  608. if ($d =~ /^(channel|device):/) {
  609. my @t = split (':', $d, 2);
  610. if (scalar (@t) != 2) {
  611. close (DEFFILE);
  612. return -$line;
  613. }
  614. $modtype = $t[0];
  615. $ccutype = $t[1];
  616. next;
  617. }
  618. if ($ccutype eq '' || $modtype eq '') {
  619. close (DEFFILE);
  620. return -$line;
  621. }
  622. my @av = split ('=', $d, 2);
  623. if (scalar (@av) != 2) {
  624. close (DEFFILE);
  625. return -$line;
  626. }
  627. if ($modtype eq 'channel') {
  628. $HMCCU_CUST_CHN_DEFAULTS{$ccutype}{$av[0]} = $av[1];
  629. }
  630. else {
  631. $HMCCU_CUST_DEV_DEFAULTS{$ccutype}{$av[0]} = $av[1];
  632. }
  633. }
  634. return 1;
  635. }
  636. ######################################################################
  637. # Find default attributes
  638. # Return template reference.
  639. ######################################################################
  640. sub HMCCU_FindDefaults ($$)
  641. {
  642. my ($hash, $common) = @_;
  643. my $type = $hash->{TYPE};
  644. my $ccutype = $hash->{ccutype};
  645. if ($type eq 'HMCCUCHN') {
  646. my ($adr, $chn) = split (':', $hash->{ccuaddr});
  647. foreach my $deftype (keys %HMCCU_CUST_CHN_DEFAULTS) {
  648. my @chnlst = split (',', $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_channels});
  649. return \%{$HMCCU_CUST_CHN_DEFAULTS{$deftype}}
  650. if ($ccutype =~ /^($deftype)$/i && grep { $_ eq $chn} @chnlst);
  651. }
  652. foreach my $deftype (keys %{$HMCCU_CHN_DEFAULTS}) {
  653. my @chnlst = split (',', $HMCCU_CHN_DEFAULTS->{$deftype}{_channels});
  654. return \%{$HMCCU_CHN_DEFAULTS->{$deftype}}
  655. if ($ccutype =~ /^($deftype)$/i && grep { $_ eq $chn} @chnlst);
  656. }
  657. }
  658. elsif ($type eq 'HMCCUDEV' || $type eq 'HMCCU') {
  659. foreach my $deftype (keys %HMCCU_CUST_DEV_DEFAULTS) {
  660. return \%{$HMCCU_CUST_DEV_DEFAULTS{$deftype}} if ($ccutype =~ /^($deftype)$/i);
  661. }
  662. foreach my $deftype (keys %{$HMCCU_DEV_DEFAULTS}) {
  663. return \%{$HMCCU_DEV_DEFAULTS->{$deftype}} if ($ccutype =~ /^($deftype)$/i);
  664. }
  665. }
  666. return undef;
  667. }
  668. ######################################################################
  669. # Set default attributes from template
  670. ######################################################################
  671. sub HMCCU_SetDefaultsTemplate ($$)
  672. {
  673. my ($hash, $template) = @_;
  674. my $name = $hash->{NAME};
  675. foreach my $a (keys %{$template}) {
  676. next if ($a =~ /^_/);
  677. my $v = $template->{$a};
  678. CommandAttr (undef, "$name $a $v");
  679. }
  680. }
  681. ######################################################################
  682. # Set default attributes
  683. ######################################################################
  684. sub HMCCU_SetDefaults ($)
  685. {
  686. my ($hash) = @_;
  687. my $name = $hash->{NAME};
  688. # Set type specific attributes
  689. my $template = HMCCU_FindDefaults ($hash, 0);
  690. return 0 if (!defined ($template));
  691. HMCCU_SetDefaultsTemplate ($hash, $template);
  692. return 1;
  693. }
  694. ######################################################################
  695. # List default attributes for device type (mode = 0) or all
  696. # device types (mode = 1) with default attributes available.
  697. ######################################################################
  698. sub HMCCU_GetDefaults ($$)
  699. {
  700. my ($hash, $mode) = @_;
  701. my $name = $hash->{NAME};
  702. my $type = $hash->{TYPE};
  703. my $ccutype = $hash->{ccutype};
  704. my $result = '';
  705. my $deffile = '';
  706. if ($mode == 0) {
  707. my $template = HMCCU_FindDefaults ($hash, 0);
  708. return ($result eq '' ? "No default attributes defined" : $result) if (!defined ($template));
  709. foreach my $a (keys %{$template}) {
  710. next if ($a =~ /^_/);
  711. my $v = $template->{$a};
  712. $result .= $a." = ".$v."\n";
  713. }
  714. }
  715. else {
  716. $result = "HMCCU Channels:\n------------------------------\n";
  717. foreach my $deftype (sort keys %{$HMCCU_CHN_DEFAULTS}) {
  718. my $tlist = $deftype;
  719. $tlist =~ s/\|/,/g;
  720. $result .= $HMCCU_CHN_DEFAULTS->{$deftype}{_description}." ($tlist), channels ".
  721. $HMCCU_CHN_DEFAULTS->{$deftype}{_channels}."\n";
  722. }
  723. $result .= "\nHMCCU Devices:\n------------------------------\n";
  724. foreach my $deftype (sort keys %{$HMCCU_DEV_DEFAULTS}) {
  725. my $tlist = $deftype;
  726. $tlist =~ s/\|/,/g;
  727. $result .= $HMCCU_DEV_DEFAULTS->{$deftype}{_description}." ($tlist)\n";
  728. }
  729. $result .= "\nCustom Channels:\n-----------------------------\n";
  730. foreach my $deftype (sort keys %HMCCU_CUST_CHN_DEFAULTS) {
  731. my $tlist = $deftype;
  732. $tlist =~ s/\|/,/g;
  733. $result .= $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_description}." ($tlist), channels ".
  734. $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_channels}."\n";
  735. }
  736. $result .= "\nCustom Devices:\n-----------------------------\n";
  737. foreach my $deftype (sort keys %HMCCU_CUST_DEV_DEFAULTS) {
  738. my $tlist = $deftype;
  739. $tlist =~ s/\|/,/g;
  740. $result .= $HMCCU_CUST_DEV_DEFAULTS{$deftype}{_description}." ($tlist)\n";
  741. }
  742. }
  743. return $result;
  744. }
  745. ######################################################################
  746. # Handle FHEM events
  747. ######################################################################
  748. sub HMCCU_Notify ($$)
  749. {
  750. my ($hash, $devhash) = @_;
  751. my $name = $hash->{NAME};
  752. my $devname = $devhash->{NAME};
  753. my $devtype = $devhash->{TYPE};
  754. my $disable = AttrVal ($name, 'disable', 0);
  755. my $rpcserver = AttrVal ($name, 'rpcserver', 'off');
  756. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  757. return if ($disable);
  758. my $events = deviceEvents ($devhash, 1);
  759. return if (! $events);
  760. # Process events
  761. foreach my $event (@{$events}) {
  762. if ($devname eq 'global') {
  763. if ($event eq 'INITIALIZED') {
  764. return if ($rpcserver eq 'off');
  765. my $delay = $HMCCU_INIT_INTERVAL0;
  766. Log3 $name, 0, "HMCCU: Start of RPC server after FHEM initialization in $delay seconds";
  767. if ($ccuflags =~ /extrpc/) {
  768. InternalTimer (gettimeofday()+$delay, "HMCCU_StartExtRPCServer", $hash, 0);
  769. }
  770. else {
  771. InternalTimer (gettimeofday()+$delay, "HMCCU_StartIntRPCServer", $hash, 0);
  772. }
  773. }
  774. }
  775. else {
  776. return if ($devtype ne 'HMCCUDEV' && $devtype ne 'HMCCUCHN');
  777. my ($r, $v) = split (": ", $event);
  778. return if (!defined ($v));
  779. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  780. return if ($ccuflags =~ /noagg/);
  781. foreach my $rule (keys %{$hash->{hmccu}{agg}}) {
  782. my $ftype = $hash->{hmccu}{agg}{$rule}{ftype};
  783. my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr};
  784. my $fread = $hash->{hmccu}{agg}{$rule}{fread};
  785. next if ($r !~ $fread);
  786. next if ($ftype eq 'name' && $devname !~ /$fexpr/);
  787. next if ($ftype eq 'type' && $devhash->{ccutype} !~ /$fexpr/);
  788. next if ($ftype eq 'group' && AttrVal ($devname, 'group', 'null') !~ /$fexpr/);
  789. next if ($ftype eq 'room' && AttrVal ($devname, 'room', 'null') !~ /$fexpr/);
  790. next if ($ftype eq 'alias' && AttrVal ($devname, 'alias', 'null') !~ /$fexpr/);
  791. HMCCU_AggregateReadings ($hash, $rule);
  792. }
  793. }
  794. }
  795. return;
  796. }
  797. ######################################################################
  798. # Calculate reading aggregations.
  799. # Called by Notify or via command get aggregation.
  800. ######################################################################
  801. sub HMCCU_AggregateReadings ($$)
  802. {
  803. my ($hash, $rule) = @_;
  804. my $dc = 0;
  805. my $mc = 0;
  806. my $result = '';
  807. my $rl = '';
  808. my $table = '';
  809. # Get rule parameters
  810. my $ftype = $hash->{hmccu}{agg}{$rule}{ftype};
  811. my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr};
  812. my $fexcl = $hash->{hmccu}{agg}{$rule}{fexcl};
  813. my $fread = $hash->{hmccu}{agg}{$rule}{fread};
  814. my $fcond = $hash->{hmccu}{agg}{$rule}{fcond};
  815. my $ftrue = $hash->{hmccu}{agg}{$rule}{ftrue};
  816. my $felse = $hash->{hmccu}{agg}{$rule}{felse};
  817. my $fpref = $hash->{hmccu}{agg}{$rule}{fpref};
  818. my $fhtml = exists ($hash->{hmccu}{agg}{$rule}{fhtml}) ? 1 : 0;
  819. my $resval;
  820. $resval = $ftrue if ($fcond =~ /^(max|min|sum|avg)$/);
  821. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  822. foreach my $d (@devlist) {
  823. my $ch = $defs{$d};
  824. my $cn = $ch->{NAME};
  825. my $ct = $ch->{TYPE};
  826. my $fmatch = '';
  827. $fmatch = $cn if ($ftype eq 'name');
  828. $fmatch = $ch->{ccutype} if ($ftype eq 'type');
  829. $fmatch = AttrVal ($cn, 'group', '') if ($ftype eq 'group');
  830. $fmatch = AttrVal ($cn, 'room', '') if ($ftype eq 'room');
  831. $fmatch = AttrVal ($cn, 'alias', '') if ($ftype eq 'alias');
  832. next if ($fmatch eq '' || $fmatch !~ /$fexpr/ || ($fexcl ne '' && $fmatch =~ /$fexcl/));
  833. my $fcoll = $hash->{hmccu}{agg}{$rule}{fcoll} eq 'NAME' ?
  834. $cn : AttrVal ($cn, $hash->{hmccu}{agg}{$rule}{fcoll}, $cn);
  835. # Compare readings
  836. foreach my $r (keys %{$ch->{READINGS}}) {
  837. next if ($r !~ /$fread/);
  838. my $rv = $ch->{READINGS}{$r}{VAL};
  839. my $f = 0;
  840. if (($fcond eq 'any' || $fcond eq 'all') && $rv =~ /$ftrue/) {
  841. $mc++;
  842. $f = 1;
  843. }
  844. if ($fcond eq 'max' && $rv > $resval) {
  845. $resval = $rv;
  846. $mc = 1;
  847. $f = 1;
  848. }
  849. if ($fcond eq 'min' && $rv < $resval) {
  850. $resval = $rv;
  851. $mc = 1;
  852. $f = 1;
  853. }
  854. if ($fcond eq 'sum' || $fcond eq 'avg') {
  855. $resval += $rv;
  856. $mc++;
  857. $f = 1;
  858. }
  859. if (($fcond eq 'gt' && $rv > $ftrue) ||
  860. ($fcond eq 'lt' && $rv < $ftrue) ||
  861. ($fcond eq 'ge' && $rv >= $ftrue) ||
  862. ($fcond eq 'le' && $rv <= $ftrue)) {
  863. $mc++;
  864. $f = 1;
  865. }
  866. if ($f) {
  867. $rl .= ($mc > 1 ? ",$fcoll" : $fcoll);
  868. last;
  869. }
  870. }
  871. $dc++;
  872. }
  873. $rl = $hash->{hmccu}{agg}{$rule}{fdflt} if ($rl eq '');
  874. # HTML code generation
  875. if ($fhtml) {
  876. if ($rl ne '') {
  877. $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}.
  878. $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-table'};
  879. $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'header'}
  880. if (exists ($hash->{hmccu}{agg}{$rule}{fhtml}{'header'}));
  881. my $row = 1;
  882. foreach my $v (split (",", $rl)) {
  883. my $t_row = ($row % 2) ? $hash->{hmccu}{agg}{$rule}{fhtml}{'row-odd'} :
  884. $hash->{hmccu}{agg}{$rule}{fhtml}{'row-even'};
  885. $t_row =~ s/\<reading\/\>/$v/;
  886. $table .= $t_row;
  887. $row++;
  888. }
  889. $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'end-table'}.
  890. $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'};
  891. }
  892. else {
  893. $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}.
  894. $hash->{hmccu}{agg}{$rule}{fhtml}{'default'}.
  895. $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'};
  896. }
  897. }
  898. if ($fcond eq 'any') {
  899. $result = $mc > 0 ? $ftrue : $felse;
  900. }
  901. elsif ($fcond eq 'all') {
  902. $result = $mc == $dc ? $ftrue : $felse;
  903. }
  904. elsif ($fcond eq 'min' || $fcond eq 'max' || $fcond eq 'sum') {
  905. $result = $mc > 0 ? $resval : $felse;
  906. }
  907. elsif ($fcond eq 'avg') {
  908. $result = $mc > 0 ? $resval/$mc : $felse;
  909. }
  910. elsif ($fcond =~ /^(gt|lt|ge|le)$/) {
  911. $result = $mc;
  912. }
  913. # Set readings
  914. readingsBeginUpdate ($hash);
  915. readingsBulkUpdate ($hash, $fpref.'state', $result);
  916. readingsBulkUpdate ($hash, $fpref.'match', $mc);
  917. readingsBulkUpdate ($hash, $fpref.'count', $dc);
  918. readingsBulkUpdate ($hash, $fpref.'list', $rl);
  919. readingsBulkUpdate ($hash, $fpref.'table', $table) if ($fhtml);
  920. readingsEndUpdate ($hash, 1);
  921. return $result;
  922. }
  923. ######################################################################
  924. # Delete device
  925. ######################################################################
  926. sub HMCCU_Undef ($$)
  927. {
  928. my ($hash, $arg) = @_;
  929. # Shutdown RPC server
  930. HMCCU_Shutdown ($hash);
  931. # Delete reference to IO module in client devices
  932. my @keylist = keys %defs;
  933. foreach my $d (@keylist) {
  934. if (exists ($defs{$d}) && exists($defs{$d}{IODev}) &&
  935. $defs{$d}{IODev} == $hash) {
  936. delete $defs{$d}{IODev};
  937. }
  938. }
  939. return undef;
  940. }
  941. ######################################################################
  942. # Shutdown FHEM
  943. ######################################################################
  944. sub HMCCU_Shutdown ($)
  945. {
  946. my ($hash) = @_;
  947. my $name = $hash->{NAME};
  948. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  949. # Shutdown RPC server
  950. if ($ccuflags =~ /extrpc/) {
  951. HMCCU_StopExtRPCServer ($hash);
  952. }
  953. else {
  954. HMCCU_StopRPCServer ($hash);
  955. }
  956. # Remove existing timer functions
  957. RemoveInternalTimer ($hash);
  958. return undef;
  959. }
  960. ######################################################################
  961. # Set commands
  962. ######################################################################
  963. sub HMCCU_Set ($@)
  964. {
  965. my ($hash, $a, $h) = @_;
  966. my $name = shift @$a;
  967. my $opt = shift @$a;
  968. my $options = "var delete execute hmscript cleardefaults:noArg defaults:noArg ".
  969. "importdefaults rpcserver:on,off datapoint";
  970. my $usage = "HMCCU: Unknown argument $opt, choose one of $options";
  971. my $host = $hash->{host};
  972. if ($opt ne 'rpcserver' && HMCCU_IsRPCStateBlocking ($hash)) {
  973. return HMCCU_SetState ($hash, "busy", "HMCCU: CCU busy, choose one of rpcserver:off");
  974. }
  975. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  976. $options .= ",restart" if ($ccuflags =~ /intrpc/);
  977. my $stripchar = AttrVal ($name, "stripchar", '');
  978. my $ccureadings = AttrVal ($name, "ccureadings", 1);
  979. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hash);
  980. my $substitute = HMCCU_GetAttrSubstitute ($hash, $hash);
  981. if ($opt eq 'var') {
  982. my $vartype;
  983. $vartype = shift @$a if (scalar (@$a) == 3);
  984. my $objname = shift @$a;
  985. my $objvalue = shift @$a;
  986. $usage = "set $name $opt [{'bool'|'list'|'number'|'test'}] variable value [param=value [...]]";
  987. my $result;
  988. return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue));
  989. $objname =~ s/$stripchar$// if ($stripchar ne '');
  990. $objvalue =~ s/\\_/%20/g;
  991. $h->{name} = $objname if (!defined ($h) && defined ($vartype));
  992. $result = HMCCU_SetVariable ($hash, $objname, $objvalue, $vartype, $h);
  993. return HMCCU_SetError ($hash, $result) if ($result < 0);
  994. return HMCCU_SetState ($hash, "OK");
  995. }
  996. elsif ($opt eq 'datapoint') {
  997. my $objname = shift @$a;
  998. my $objvalue = shift @$a;
  999. $usage = "Usage: set $name $opt {ccuobject|'hmccu':fhemobject} value";
  1000. return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue));
  1001. my $rc = HMCCU_SetDatapoint ($hash, $objname, $objvalue);
  1002. return HMCCU_SetError ($hash, $rc) if ($rc < 0);
  1003. return HMCCU_SetState ($hash, "OK");
  1004. }
  1005. elsif ($opt eq 'delete') {
  1006. my $objname = shift @$a;
  1007. my $objtype = shift @$a;
  1008. $objtype = "OT_VARDP" if (!defined ($objtype));
  1009. $usage = "Usage: set $name $opt ccuobject ['OT_VARDP'|'OT_DEVICE']";
  1010. return HMCCU_SetError ($hash, $usage)
  1011. if (!defined ($objname) || $objtype !~ /^(OT_VARDP|OT_DEVICE)$/);
  1012. my $result = HMCCU_HMScriptExt ($hash, "!DeleteObject", { name => $objname, type => $objtype });
  1013. return HMCCU_SetError ($hash, -2) if ($result =~ /^ERROR:.*/);
  1014. return HMCCU_SetState ($hash, "OK");
  1015. }
  1016. elsif ($opt eq 'execute') {
  1017. my $program = shift @$a;
  1018. my $response;
  1019. $usage = "Usage: set $name $opt program-name";
  1020. return HMCCU_SetError ($hash, $usage) if (!defined ($program));
  1021. my $url = qq(http://$host:8181/do.exe?r1=dom.GetObject("$program").ProgramExecute());
  1022. $response = GetFileFromURL ($url);
  1023. $response =~ m/<r1>(.*)<\/r1>/;
  1024. my $value = $1;
  1025. return HMCCU_SetState ($hash, "OK") if (defined ($value) && $value ne '' && $value ne 'null');
  1026. return HMCCU_SetError ($hash, "Program execution error");
  1027. }
  1028. elsif ($opt eq 'hmscript') {
  1029. my $script = shift @$a;
  1030. my $dump = shift @$a;
  1031. my $response = '';
  1032. my %objects = ();
  1033. my $objcount = 0;
  1034. $usage = "Usage: set $name $opt {file|!function|'['code']'} ['dump'] [parname=value [...]]";
  1035. # If no parameter is specified list available script functions
  1036. if (!defined ($script)) {
  1037. $response = "Available HomeMatic script functions:\n".
  1038. "-------------------------------------\n";
  1039. foreach my $scr (keys %{$HMCCU_SCRIPTS}) {
  1040. $response .= "$scr ".$HMCCU_SCRIPTS->{$scr}{syntax}."\n".
  1041. $HMCCU_SCRIPTS->{$scr}{description}."\n\n";
  1042. }
  1043. }
  1044. $response .= $usage;
  1045. return HMCCU_SetError ($hash, $response)
  1046. if (!defined ($script) || (defined ($dump) && $dump ne 'dump'));
  1047. $response = HMCCU_HMScriptExt ($hash, $script, $h);
  1048. return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/);
  1049. HMCCU_SetState ($hash, "OK");
  1050. return $response if (! $ccureadings || defined ($dump));
  1051. foreach my $line (split /\n/, $response) {
  1052. my @tokens = split /=/, $line;
  1053. next if (@tokens != 2);
  1054. my $reading;
  1055. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $tokens[0],
  1056. $HMCCU_FLAG_INTERFACE);
  1057. ($add, $chn) = HMCCU_GetAddress ($hash, $nam, '', '') if ($flags == $HMCCU_FLAGS_NCD);
  1058. if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) {
  1059. $objects{$add}{$chn}{$dpt} = $tokens[1];
  1060. $objcount++;
  1061. }
  1062. else {
  1063. # If output is not related to a channel store reading in I/O device
  1064. my $Value = HMCCU_Substitute ($tokens[1], $substitute, 0, undef, $tokens[0]);
  1065. my $rn = $tokens[0];
  1066. $rn =~ s/\:/\./g;
  1067. $rn =~ s/[^A-Za-z\d_\.-]+/_/g;
  1068. readingsSingleUpdate ($hash, $rn, $Value, 1);
  1069. }
  1070. }
  1071. HMCCU_UpdateMultipleDevices ($hash, \%objects) if ($objcount > 0);
  1072. return defined ($dump) ? $response : undef;
  1073. }
  1074. elsif ($opt eq 'rpcserver') {
  1075. my $action = shift @$a;
  1076. $usage = "Usage: set $name $opt {'on'|'off'|'restart'}";
  1077. return HMCCU_SetError ($hash, $usage)
  1078. if (!defined ($action) || $action !~ /^(on|off|restart)$/);
  1079. if ($action eq 'on') {
  1080. if ($ccuflags =~ /extrpc/) {
  1081. return HMCCU_SetError ($hash, "Start of RPC server failed")
  1082. if (!HMCCU_StartExtRPCServer ($hash));
  1083. }
  1084. else {
  1085. return HMCCU_SetError ($hash, "Start of RPC server failed")
  1086. if (!HMCCU_StartIntRPCServer ($hash));
  1087. }
  1088. }
  1089. elsif ($action eq 'off') {
  1090. if ($ccuflags =~ /extrpc/) {
  1091. return HMCCU_SetError ($hash, "Stop of RPC server failed")
  1092. if (!HMCCU_StopExtRPCServer ($hash));
  1093. }
  1094. else {
  1095. return HMCCU_SetError ($hash, "Stop of RPC server failed")
  1096. if (!HMCCU_StopRPCServer ($hash));
  1097. }
  1098. }
  1099. elsif ($action eq 'restart') {
  1100. return "HMCCU: No RPC server running" if (!HMCCU_IsRPCServerRunning ($hash, undef, undef));
  1101. if ($ccuflags =~ /intrpc/) {
  1102. return "HMCCU: Can't stop RPC server" if (!HMCCURPC_StopRPCServer ($hash));
  1103. $hash->{RPCState} = "restarting";
  1104. readingsSingleUpdate ($hash, "rpcstate", "restarting", 1);
  1105. DoTrigger ($name, "RPC server restarting");
  1106. }
  1107. else {
  1108. return HMCCU_SetError ($hash, "HMCCU: restart not supported by external RPC server");
  1109. }
  1110. }
  1111. return HMCCU_SetState ($hash, "OK");
  1112. }
  1113. elsif ($opt eq 'defaults') {
  1114. my $rc = HMCCU_SetDefaults ($hash);
  1115. return HMCCU_SetError ($hash, "HMCCU: No default attributes found") if ($rc == 0);
  1116. return HMCCU_SetState ($hash, "OK");
  1117. }
  1118. elsif ($opt eq 'cleardefaults') {
  1119. %HMCCU_CUST_CHN_DEFAULTS = ();
  1120. %HMCCU_CUST_DEV_DEFAULTS = ();
  1121. return HMCCU_SetState ($hash, "OK", "Default attributes deleted");
  1122. }
  1123. elsif ($opt eq 'importdefaults') {
  1124. my $filename = shift @$a;
  1125. $usage = "Usage: set $name $opt filename";
  1126. return HMCCU_SetError ($hash, $usage) if (!defined ($filename));
  1127. my $rc = HMCCU_ImportDefaults ($filename);
  1128. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  1129. if ($rc < 0) {
  1130. $rc = -$rc;
  1131. return HMCCU_SetError ($hash, "Syntax error in default attribute file $filename line $rc");
  1132. }
  1133. return HMCCU_SetState ($hash, "OK", "Default attributes read from file $filename");
  1134. }
  1135. else {
  1136. return $usage;
  1137. }
  1138. }
  1139. ######################################################################
  1140. # Get commands
  1141. ######################################################################
  1142. sub HMCCU_Get ($@)
  1143. {
  1144. my ($hash, $a, $h) = @_;
  1145. my $name = shift @$a;
  1146. my $opt = shift @$a;
  1147. my $options = "defaults:noArg exportdefaults devicelist dump dutycycle:noArg vars update".
  1148. " updateccu parfile configdesc firmware:noArg rpcevents:noArg rpcstate:noArg deviceinfo";
  1149. my $usage = "HMCCU: Unknown argument $opt, choose one of $options";
  1150. my $host = $hash->{host};
  1151. if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash)) {
  1152. return HMCCU_SetState ($hash, "busy", "HMCCU: CCU busy, choose one of rpcstate:noArg");
  1153. }
  1154. my $ccuflags = AttrVal ($name, "ccuflags", "null");
  1155. my $ccureadings = AttrVal ($name, "ccureadings", 1);
  1156. my $parfile = AttrVal ($name, "parfile", '');
  1157. my $readname;
  1158. my $readaddr;
  1159. my $result = '';
  1160. my $rc;
  1161. if ($opt eq 'dump') {
  1162. my $content = shift @$a;
  1163. my $filter = shift @$a;
  1164. $filter = '.*' if (!defined ($filter));
  1165. $usage = "Usage: get $name dump {'datapoints'|'devtypes'} [filter]";
  1166. my %foper = (1, "R", 2, "W", 4, "E", 3, "RW", 5, "RE", 6, "WE", 7, "RWE");
  1167. my %ftype = (2, "B", 4, "F", 16, "I", 20, "S");
  1168. return HMCCU_SetError ($hash, $usage) if (!defined ($content));
  1169. if ($content eq 'devtypes') {
  1170. foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) {
  1171. $result .= $devtype."\n" if ($devtype =~ /$filter/);
  1172. }
  1173. }
  1174. elsif ($content eq 'datapoints') {
  1175. foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) {
  1176. next if ($devtype !~ /$filter/);
  1177. foreach my $chn (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}}) {
  1178. foreach my $dpt (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  1179. my $t = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{type};
  1180. my $o = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{oper};
  1181. $result .= $devtype.".".$chn.".".$dpt." [".
  1182. (exists($ftype{$t}) ? $ftype{$t} : $t)."] [".
  1183. (exists($foper{$o}) ? $foper{$o} : $o)."]\n";
  1184. }
  1185. }
  1186. }
  1187. }
  1188. else {
  1189. return HMCCU_SetError ($hash, $usage);
  1190. }
  1191. return HMCCU_SetState ($hash, "OK", ($result eq '') ? "No data found" : $result);
  1192. }
  1193. elsif ($opt eq 'vars') {
  1194. my $varname = shift @$a;
  1195. $usage = "Usage: get $name vars {regexp}[,...]";
  1196. return HMCCU_SetError ($hash, $usage) if (!defined ($varname));
  1197. ($rc, $result) = HMCCU_GetVariables ($hash, $varname);
  1198. return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
  1199. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1200. }
  1201. elsif ($opt eq 'update' || $opt eq 'updateccu') {
  1202. my $devexp = shift @$a;
  1203. $devexp = '.*' if (!defined ($devexp));
  1204. $usage = "Usage: get $name $opt [device-expr [{'State'|'Value'}]]";
  1205. my $ccuget = shift @$a;
  1206. $ccuget = 'Attr' if (!defined ($ccuget));
  1207. return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/);
  1208. my ($co, $ce) = HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0);
  1209. return HMCCU_SetState ($hash, "OK",
  1210. "$co client devices successfully updated. Update for $ce client devices failed");
  1211. }
  1212. elsif ($opt eq 'parfile') {
  1213. my $par_parfile = shift @$a;
  1214. my @parameters;
  1215. my $parcount;
  1216. if (defined ($par_parfile)) {
  1217. $parfile = $par_parfile;
  1218. }
  1219. else {
  1220. return HMCCU_SetError ($hash, "No parameter file specified") if ($parfile eq '');
  1221. }
  1222. # Read parameter file
  1223. if (open (PARFILE, "<$parfile")) {
  1224. @parameters = <PARFILE>;
  1225. $parcount = scalar @parameters;
  1226. close (PARFILE);
  1227. }
  1228. else {
  1229. return HMCCU_SetError ($hash, -16);
  1230. }
  1231. return HMCCU_SetError ($hash, "Empty parameter file") if ($parcount < 1);
  1232. ($rc, $result) = HMCCU_GetChannel ($hash, \@parameters);
  1233. return HMCCU_SetError ($hash, $rc) if ($rc < 0);
  1234. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1235. }
  1236. elsif ($opt eq 'deviceinfo') {
  1237. my $device = shift @$a;
  1238. $usage = "Usage: get $name $opt device [{'State'|'Value'}]";
  1239. return HMCCU_SetError ($hash, $usage) if (!defined ($device));
  1240. my $ccuget = shift @$a;
  1241. $ccuget = 'Attr' if (!defined ($ccuget));
  1242. return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/);
  1243. return HMCCU_SetError ($hash, -1) if (!HMCCU_IsValidDeviceOrChannel ($hash, $device));
  1244. $result = HMCCU_GetDeviceInfo ($hash, $device, $ccuget);
  1245. return HMCCU_SetError ($hash, -2) if ($result eq '' || $result =~ /^ERROR:.*/);
  1246. HMCCU_SetState ($hash, "OK");
  1247. return HMCCU_FormatDeviceInfo ($result);
  1248. }
  1249. elsif ($opt eq 'rpcevents') {
  1250. if ($ccuflags =~ /intrpc/) {
  1251. return HMCCU_SetError ($hash, "No event statistics available")
  1252. if (!exists ($hash->{hmccu}{evs}) || !exists ($hash->{hmccu}{evr}));
  1253. foreach my $stkey (sort keys %{$hash->{hmccu}{evr}}) {
  1254. $result .= "S: ".$stkey." = ".$hash->{hmccu}{evs}{$stkey}."\n";
  1255. $result .= "R: ".$stkey." = ".$hash->{hmccu}{evr}{$stkey}."\n";
  1256. }
  1257. return HMCCU_SetState ($hash, "OK", $result);
  1258. }
  1259. else {
  1260. my $rpcdev = HMCCU_GetRPCDevice ($hash, 0);
  1261. return HMCCU_SetError ($hash, "HMCCU: External RPC server not found") if ($rpcdev eq '');
  1262. $result = AnalyzeCommandChain (undef, "get $rpcdev rpcevents");
  1263. return HMCCU_SetState ($hash, "OK", $result) if (defined ($result));
  1264. return HMCCU_SetError ($hash, "No event statistics available");
  1265. }
  1266. }
  1267. elsif ($opt eq 'rpcstate') {
  1268. my @hm_pids = ();
  1269. my @hm_tids = ();
  1270. $result = "No RPC processes or threads are running";
  1271. if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) {
  1272. $result = "RPC process(es) running with pid(s) ".
  1273. join (',', @hm_pids) if (scalar (@hm_pids) > 0);
  1274. $result = "RPC thread(s) running with tid(s) ".
  1275. join (',', @hm_tids) if (scalar (@hm_tids) > 0);
  1276. }
  1277. return HMCCU_SetState ($hash, "OK", $result);
  1278. }
  1279. elsif ($opt eq 'devicelist') {
  1280. my ($devcount, $chncount) = HMCCU_GetDeviceList ($hash);
  1281. return HMCCU_SetError ($hash, -2) if ($devcount < 0);
  1282. return HMCCU_SetError ($hash, "No devices received from CCU") if ($devcount == 0);
  1283. $result = "Read $devcount devices with $chncount channels from CCU";
  1284. my $optcmd = shift @$a;
  1285. if (defined ($optcmd)) {
  1286. if ($optcmd eq 'dump') {
  1287. $result .= "\n-----------------------------------------\n";
  1288. my $n = 0;
  1289. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  1290. if ($hash->{hmccu}{dev}{$add}{addtype} eq 'dev') {
  1291. $result .= "Device ".'"'.$hash->{hmccu}{dev}{$add}{name}.'"'." [".$add."] ".
  1292. "Type=".$hash->{hmccu}{dev}{$add}{type}."\n";
  1293. $n = 0;
  1294. }
  1295. else {
  1296. $result .= " Channel $n ".'"'.$hash->{hmccu}{dev}{$add}{name}.'"'.
  1297. " [".$add."]\n";
  1298. $n++;
  1299. }
  1300. }
  1301. return $result;
  1302. }
  1303. elsif ($optcmd eq 'create') {
  1304. $usage = "Usage: get $name create {devexp|chnexp} [t={'chn'|'dev'|'all'}] [s=suffix] ".
  1305. "[p=prefix] [f=format] ['defattr'] ['duplicates'] [save] [attr=val [...]]";
  1306. my $devdefaults = 0;
  1307. my $duplicates = 0;
  1308. my $savedef = 0;
  1309. my $newcount = 0;
  1310. # Process command line parameters
  1311. my $devspec = shift @$a;
  1312. my $devprefix = exists ($h->{p}) ? $h->{p} : '';
  1313. my $devsuffix = exists ($h->{'s'}) ? $h->{'s'} : '';
  1314. my $devtype = exists ($h->{t}) ? $h->{t} : 'dev';
  1315. my $devformat = exists ($h->{f}) ? $h->{f} : '%n';
  1316. return HMCCU_SetError ($hash, $usage)
  1317. if ($devtype !~ /^(dev|chn|all)$/ || !defined ($devspec));
  1318. foreach my $defopt (@$a) {
  1319. if ($defopt eq 'defattr') { $devdefaults = 1; }
  1320. elsif ($defopt eq 'duplicates') { $duplicates = 1; }
  1321. elsif ($defopt eq 'save') { $savedef = 1; }
  1322. else { return HMCCU_SetError ($hash, $usage); }
  1323. }
  1324. # Get list of existing client devices
  1325. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  1326. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  1327. my $defmod = $hash->{hmccu}{dev}{$add}{addtype} eq 'dev' ? 'HMCCUDEV' : 'HMCCUCHN';
  1328. my $ccuname = $hash->{hmccu}{dev}{$add}{name};
  1329. my $ccudevname = HMCCU_GetDeviceName ($hash, $add, $ccuname);
  1330. next if ($devtype ne 'all' && $devtype ne $hash->{hmccu}{dev}{$add}{addtype});
  1331. next if (HMCCU_ExprNotMatch ($ccuname, $devspec, 1));
  1332. # Build FHEM device name
  1333. my $devname = $devformat;
  1334. $devname = $devprefix.$devname.$devsuffix;
  1335. $devname =~ s/%n/$ccuname/g;
  1336. $devname =~ s/%d/$ccudevname/g;
  1337. $devname =~ s/%a/$add/g;
  1338. $devname =~ s/[^A-Za-z\d_\.]+/_/g;
  1339. # Check for duplicate device definitions
  1340. if (!$duplicates) {
  1341. next if (exists ($defs{$devname}));
  1342. my $devexists = 0;
  1343. foreach my $exdev (@devlist) {
  1344. if ($defs{$exdev}->{ccuaddr} eq $add) {
  1345. $devexists = 1;
  1346. last;
  1347. }
  1348. }
  1349. next if ($devexists);
  1350. }
  1351. # Define new client device
  1352. my $ret = CommandDefine (undef, $devname." $defmod ".$add);
  1353. if ($ret) {
  1354. Log3 $name, 2, "HMCCU: Define command failed $devname $defmod $ccuname";
  1355. Log3 $name, 2, "$defmod: $ret";
  1356. $result .= "\nCan't create device $devname. $ret";
  1357. next;
  1358. }
  1359. # Set device attributes
  1360. HMCCU_SetDefaults ($defs{$devname}) if ($devdefaults);
  1361. foreach my $da (keys %$h) {
  1362. next if ($da =~ /^[pstf]$/);
  1363. $ret = CommandAttr (undef, "$devname $da ".$h->{$da});
  1364. if ($ret) {
  1365. Log3 $name, 2, "HMCCU: Attr command failed $devname $da ".$h->{$da};
  1366. Log3 $name, 2, "$defmod: $ret";
  1367. }
  1368. }
  1369. Log3 $name, 2, "$defmod: Created device $devname";
  1370. $result .= "\nCreated device $devname";
  1371. $newcount++;
  1372. }
  1373. CommandSave (undef, undef) if ($newcount > 0 && $savedef);
  1374. $result .= "\nCreated $newcount client devices";
  1375. }
  1376. }
  1377. return HMCCU_SetState ($hash, "OK", $result);
  1378. }
  1379. elsif ($opt eq 'dutycycle') {
  1380. my $dc = HMCCU_GetDutyCycle ($hash);
  1381. return HMCCU_SetState ($hash, "OK", "Read $dc duty cycle values");
  1382. }
  1383. elsif ($opt eq 'firmware') {
  1384. my $dc = HMCCU_GetFirmwareVersions ($hash);
  1385. return "Found no firmware downloads" if ($dc == 0);
  1386. $result = "Found $dc firmware downloads.";
  1387. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  1388. return $result if (scalar (@devlist) == 0);
  1389. $result .= " Click on the new version number for download\n\n".
  1390. "Device Type Current Available Date\n".
  1391. "------------------------------------------------------------------------\n";
  1392. foreach my $dev (@devlist) {
  1393. my $ch = $defs{$dev};
  1394. my $ct = uc($ch->{ccutype});
  1395. next if (!defined ($ch->{firmware}));
  1396. next if (!exists ($hash->{hmccu}{type}{$ct}));
  1397. $result .= sprintf "%-25s %-20s %-7s <a href=\"http://www.eq-3.de/%s\">%-9s</a> %-10s\n",
  1398. $ch->{NAME}, $ct, $ch->{firmware}, $hash->{hmccu}{type}{$ct}{download},
  1399. $hash->{hmccu}{type}{$ct}{firmware}, $hash->{hmccu}{type}{$ct}{date};
  1400. }
  1401. return HMCCU_SetState ($hash, "OK", $result);
  1402. }
  1403. elsif ($opt eq 'defaults') {
  1404. $result = HMCCU_GetDefaults ($hash, 1);
  1405. return HMCCU_SetState ($hash, "OK", $result);
  1406. }
  1407. elsif ($opt eq 'exportdefaults') {
  1408. my $filename = shift @$a;
  1409. $usage = "Usage: get $name $opt filename";
  1410. return HMCCU_SetError ($hash, $usage) if (!defined ($filename));
  1411. my $rc = HMCCU_ExportDefaults ($filename);
  1412. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  1413. return HMCCU_SetState ($hash, "OK", "Default attributes written to $filename");
  1414. }
  1415. elsif ($opt eq 'aggregation') {
  1416. my $rule = shift @$a;
  1417. $usage = "Usage: get $name $opt {'all'|'rule'}";
  1418. return HMCCU_SetError ($hash, $usage) if (!defined ($rule));
  1419. if ($rule eq 'all') {
  1420. foreach my $r (keys %{$hash->{hmccu}{agg}}) {
  1421. my $rc = HMCCU_AggregateReadings ($hash, $r);
  1422. $result .= "$r = $rc\n";
  1423. }
  1424. }
  1425. else {
  1426. return HMCCU_SetError ($hash, "HMCCU: Aggregation rule does not exist")
  1427. if (!exists ($hash->{hmccu}{agg}{$rule}));
  1428. $result = HMCCU_AggregateReadings ($hash, $rule);
  1429. $result = "$rule = $result";
  1430. }
  1431. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1432. }
  1433. elsif ($opt eq 'configdesc') {
  1434. my $ccuobj = shift @$a;
  1435. $usage = "Usage: get $name $opt {device|channel}";
  1436. return HMCCU_SetError ($hash, $usage) if (!defined ($ccuobj));
  1437. my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", undef);
  1438. return HMCCU_SetError ($hash, $rc) if ($rc < 0);
  1439. return HMCCU_SetState ($hash, "OK", $res);
  1440. }
  1441. else {
  1442. if (exists ($hash->{hmccu}{agg})) {
  1443. my @rules = keys %{$hash->{hmccu}{agg}};
  1444. $usage .= " aggregation:all,".join (',', @rules) if (scalar (@rules) > 0);
  1445. }
  1446. return $usage;
  1447. }
  1448. }
  1449. ######################################################################
  1450. # Parse CCU object specification.
  1451. # Supports classic Homematic and Homematic-IP addresses.
  1452. # Supports team addresses with leading * for BidCos-RF.
  1453. # Supports CCU virtual remote addresses (BidCoS:ChnNo)
  1454. #
  1455. # Possible syntax for datapoints:
  1456. # Interface.Address:Channel.Datapoint
  1457. # Address:Channel.Datapoint
  1458. # Channelname.Datapoint
  1459. #
  1460. # Possible syntax for channels:
  1461. # Interface.Address:Channel
  1462. # Address:Channel
  1463. # Channelname
  1464. #
  1465. # If object name doesn't match the rules above it's treated as name.
  1466. # With parameter flags one can specify if result is filled up with
  1467. # default values for interface or datapoint.
  1468. #
  1469. # Return list of detected attributes (empty string if attribute is
  1470. # not detected):
  1471. # (Interface, Address, Channel, Datapoint, Name, Flags)
  1472. # Flags is a bitmask of detected attributes.
  1473. ######################################################################
  1474. sub HMCCU_ParseObject ($$$)
  1475. {
  1476. my ($hash, $object, $flags) = @_;
  1477. my ($i, $a, $c, $d, $n, $f) = ('', '', '', '', '', '', 0);
  1478. if ($object =~ /^(.+?)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})\.(.+)$/ ||
  1479. $object =~ /^(.+?)\.([0-9A-F]{12,14}):([0-9]{1,2})\.(.+)$/ ||
  1480. $object =~ /^(.+?)\.(OL-.+):([0-9]{1,2})\.(.+)$/ ||
  1481. $object =~ /^(.+?)\.(BidCoS-RF):([0-9]{1,2})\.(.+)$/) {
  1482. #
  1483. # Interface.Address:Channel.Datapoint [30=11110]
  1484. #
  1485. $f = $HMCCU_FLAGS_IACD;
  1486. ($i, $a, $c, $d) = ($1, $2, $3, $4);
  1487. }
  1488. elsif ($object =~ /^(.+)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ ||
  1489. $object =~ /^(.+)\.([0-9A-F]{12,14}):([0-9]{1,2})$/ ||
  1490. $object =~ /^(.+)\.(OL-.+):([0-9]{1,2})$/ ||
  1491. $object =~ /^(.+)\.(BidCoS-RF):([0-9]{1,2})$/) {
  1492. #
  1493. # Interface.Address:Channel [26=11010]
  1494. #
  1495. $f = $HMCCU_FLAGS_IAC | ($flags & $HMCCU_FLAG_DATAPOINT);
  1496. ($i, $a, $c, $d) = ($1, $2, $3, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1497. }
  1498. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]){1,2}\.(.+)$/ ||
  1499. $object =~ /^([0-9A-F]{12,14}):([0-9]{1,2})\.(.+)$/ ||
  1500. $object =~ /^(OL-.+):([0-9]{1,2})\.(.+)$/ ||
  1501. $object =~ /^(BidCoS-RF):([0-9]{1,2})\.(.+)$/) {
  1502. #
  1503. # Address:Channel.Datapoint [14=01110]
  1504. #
  1505. $f = $HMCCU_FLAGS_ACD;
  1506. ($a, $c, $d) = ($1, $2, $3);
  1507. }
  1508. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ ||
  1509. $object =~ /^([0-9A-Z]{12,14}):([0-9]{1,2})$/ ||
  1510. $object =~ /^(OL-.+):([0-9]{1,2})$/ ||
  1511. $object =~ /^(BidCoS-RF):([0-9]{1,2})$/) {
  1512. #
  1513. # Address:Channel [10=01010]
  1514. #
  1515. $f = $HMCCU_FLAGS_AC | ($flags & $HMCCU_FLAG_DATAPOINT);
  1516. ($a, $c, $d) = ($1, $2, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1517. }
  1518. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7})$/ ||
  1519. $object =~ /^([0-9A-Z]{12,14})$/ ||
  1520. $object =~ /^(OL-.+)$/ ||
  1521. $object eq 'BidCoS') {
  1522. #
  1523. # Address
  1524. #
  1525. $f = $HMCCU_FLAG_ADDRESS;
  1526. $a = $1;
  1527. }
  1528. elsif ($object =~ /^(.+?)\.([A-Z_]+)$/) {
  1529. #
  1530. # Name.Datapoint
  1531. #
  1532. $f = $HMCCU_FLAGS_ND;
  1533. ($n, $d) = ($1, $2);
  1534. }
  1535. elsif ($object =~ /^.+$/) {
  1536. #
  1537. # Name [1=00001]
  1538. #
  1539. $f = $HMCCU_FLAG_NAME | ($flags & $HMCCU_FLAG_DATAPOINT);
  1540. ($n, $d) = ($object, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1541. }
  1542. else {
  1543. $f = 0;
  1544. }
  1545. # Check if name is a valid channel name
  1546. if ($f & $HMCCU_FLAG_NAME) {
  1547. my ($add, $chn) = HMCCU_GetAddress ($hash, $n, '', '');
  1548. if ($chn ne '') {
  1549. $f = $f | $HMCCU_FLAG_CHANNEL;
  1550. }
  1551. if ($flags & $HMCCU_FLAG_FULLADDR) {
  1552. ($i, $a, $c) = (HMCCU_GetDeviceInterface ($hash, $add, 'BidCos-RF'), $add, $chn);
  1553. $f |= $HMCCU_FLAG_INTERFACE;
  1554. $f |= $HMCCU_FLAG_ADDRESS if ($add ne '');
  1555. $f |= $HMCCU_FLAG_CHANNEL if ($chn ne '');
  1556. }
  1557. }
  1558. elsif ($f & $HMCCU_FLAG_ADDRESS && $i eq '' &&
  1559. ($flags & $HMCCU_FLAG_FULLADDR || $flags & $HMCCU_FLAG_INTERFACE)) {
  1560. $i = HMCCU_GetDeviceInterface ($hash, $a, 'BidCos-RF');
  1561. $f |= $HMCCU_FLAG_INTERFACE;
  1562. }
  1563. return ($i, $a, $c, $d, $n, $f);
  1564. }
  1565. ######################################################################
  1566. # Filter reading by datapoint and optionally by channel name or
  1567. # channel address.
  1568. # Parameter channel can be a channel name or a channel address without
  1569. # interface specification.
  1570. # Filter rule syntax is either:
  1571. # [N:]{Channel-Number|Channel-Name-Expr}!Datapoint-Expr
  1572. # or
  1573. # [N:][Channel-Number.]Datapoint-Expr
  1574. # Multiple filter rules must be separated by ;
  1575. ######################################################################
  1576. sub HMCCU_FilterReading ($$$)
  1577. {
  1578. my ($hash, $chn, $dpt) = @_;
  1579. my $name = $hash->{NAME};
  1580. my $fnc = "FilterReading";
  1581. my $hmccu_hash = HMCCU_GetHash ($hash);
  1582. return 1 if (!defined ($hmccu_hash));
  1583. my $grf = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingfilter', '');
  1584. $grf = '.*' if ($grf eq '');
  1585. my $rf = AttrVal ($name, 'ccureadingfilter', $grf);
  1586. $rf = $grf.";".$rf if ($rf ne $grf && $grf ne '.*' && $grf ne '');
  1587. my $chnnam = '';
  1588. my $chnnum = '';
  1589. my $devadd = '';
  1590. # Get channel name and channel number
  1591. if (HMCCU_IsChnAddr ($chn, 0)) {
  1592. $chnnam = HMCCU_GetChannelName ($hmccu_hash, $chn, '');
  1593. ($devadd, $chnnum) = HMCCU_SplitChnAddr ($chn);
  1594. }
  1595. else {
  1596. ($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn, '', '');
  1597. $chnnam = $chn;
  1598. }
  1599. HMCCU_Trace ($hash, 2, $fnc, "chn=$chn, chnnam=$chnnam chnnum=$chnnum dpt=$dpt, rules=$rf");
  1600. foreach my $r (split (';', $rf)) {
  1601. my $rm = 1;
  1602. my $cn = '';
  1603. # Negative filter
  1604. if ($r =~ /^N:/) {
  1605. $rm = 0;
  1606. $r =~ s/^N://;
  1607. }
  1608. # Get filter criteria
  1609. my ($c, $f) = split ("!", $r);
  1610. if (defined ($f)) {
  1611. next if ($c eq '' || $chnnam eq '' || $chnnum eq '');
  1612. $cn = $c if ($c =~ /^([0-9]{1,2})$/);
  1613. }
  1614. else {
  1615. $c = '';
  1616. if ($r =~ /^([0-9]{1,2})\.(.+)$/) {
  1617. $cn = $1;
  1618. $f = $2;
  1619. }
  1620. else {
  1621. $cn = '';
  1622. $f = $r;
  1623. }
  1624. }
  1625. HMCCU_Trace ($hash, 2, undef, " check rm=$rm f=$f cn=$cn c=$c");
  1626. # Positive filter
  1627. return 1 if (
  1628. $rm && (
  1629. (
  1630. ($cn ne '' && "$chnnum" eq "$cn") ||
  1631. ($c ne '' && $chnnam =~ /$c/) ||
  1632. ($cn eq '' && $c eq '')
  1633. ) && $dpt =~ /$f/
  1634. )
  1635. );
  1636. # Negative filter
  1637. return 1 if (
  1638. !$rm && (
  1639. ($cn ne '' && "$chnnum" ne "$cn") ||
  1640. ($c ne '' && $chnnam !~ /$c/) ||
  1641. $dpt !~ /$f/
  1642. )
  1643. );
  1644. HMCCU_Trace ($hash, 2, undef, " check result false");
  1645. }
  1646. return 0;
  1647. }
  1648. ######################################################################
  1649. # Build reading name
  1650. #
  1651. # Parameters:
  1652. #
  1653. # Interface,Address,ChannelNo,Datapoint,ChannelNam,ReadingFormat
  1654. # ReadingFormat := { name[lc] | datapoint[lc] | address[lc] }
  1655. #
  1656. # Valid combinations:
  1657. #
  1658. # ChannelName,Datapoint
  1659. # Address,Datapoint
  1660. # Address,ChannelNo,Datapoint
  1661. #
  1662. # Reading names can be modified by setting attribut ccureadingname.
  1663. # Returns list of readings names.
  1664. ######################################################################
  1665. sub HMCCU_GetReadingName ($$$$$$$)
  1666. {
  1667. my ($hash, $i, $a, $c, $d, $n, $rf) = @_;
  1668. my $name = $hash->{NAME};
  1669. my $hmccu_hash = HMCCU_GetHash ($hash);
  1670. return '' if (!defined ($hmccu_hash));
  1671. my $rn = '';
  1672. my @rnlist;
  1673. Log3 $name, 1, "HMCCU: ChannelNo undefined: Addr=".$a if (!defined ($c));
  1674. $rf = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash) if (!defined ($rf));
  1675. my $gsr = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingname', '');
  1676. my $sr = AttrVal ($name, 'ccureadingname', $gsr);
  1677. $sr .= ";".$gsr if ($sr ne $gsr && $gsr ne '');
  1678. # Datapoint is mandatory
  1679. return '' if ($d eq '');
  1680. if ($rf eq 'datapoint' || $rf eq 'datapointlc') {
  1681. $rn = (defined ($c) && $c ne '') ? $c.'.'.$d : $d;
  1682. }
  1683. elsif ($rf eq 'name' || $rf eq 'namelc') {
  1684. if ($n eq '') {
  1685. if ($a ne '' && $c ne '') {
  1686. $n = HMCCU_GetChannelName ($hmccu_hash, $a.':'.$c, '');
  1687. }
  1688. elsif ($a ne '' && $c eq '') {
  1689. $n = HMCCU_GetDeviceName ($hmccu_hash, $a, '');
  1690. }
  1691. else {
  1692. return '';
  1693. }
  1694. }
  1695. # Substitue unsupported characters in reading name
  1696. $n = HMCCU_CorrectName ($n);
  1697. return '' if ($n eq '');
  1698. $rn = $n.'.'.$d;
  1699. }
  1700. elsif ($rf eq 'address' || $rf eq 'addresslc') {
  1701. if ($a eq '' && $n ne '') {
  1702. ($a, $c) = HMCCU_GetAddress ($hmccu_hash, $n, '', '');
  1703. }
  1704. if ($a ne '') {
  1705. my $t = $a;
  1706. $i = HMCCU_GetDeviceInterface ($hmccu_hash, $a, '') if ($i eq '');
  1707. $t = $i.'.'.$t if ($i ne '');
  1708. $t = $t.'.'.$c if ($c ne '');
  1709. $rn = $t.'.'.$d;
  1710. }
  1711. }
  1712. push (@rnlist, $rn);
  1713. # Rename and/or add reading names
  1714. if ($sr ne '') {
  1715. my @rules = split (';', $sr);
  1716. foreach my $rr (@rules) {
  1717. my ($rold, $rnew) = split (':', $rr);
  1718. next if (!defined ($rnew));
  1719. if ($rnlist[0] =~ /$rold/) {
  1720. if ($rnew =~ /^\+(.+)$/) {
  1721. my $radd = $1;
  1722. $radd =~ s/$rold/$radd/;
  1723. push (@rnlist, $radd);
  1724. }
  1725. else {
  1726. $rnlist[0] =~ s/$rold/$rnew/;
  1727. }
  1728. }
  1729. }
  1730. }
  1731. # Convert to lowercase
  1732. $rnlist[0] = lc($rnlist[0]) if ($rf =~ /lc$/);
  1733. return @rnlist;
  1734. }
  1735. ######################################################################
  1736. # Format reading value depending on attribute stripnumber. Integer
  1737. # values are ignored.
  1738. # Syntax of attribute stripnumber:
  1739. # [datapoint-expr!]format[;...]
  1740. # Valid formats:
  1741. # 0 = Preserve all digits (default)
  1742. # 1 = Preserve 1 digit
  1743. # 2 = Remove trailing zeroes
  1744. # -n = Round value to specified number of digits (-0 is allowed)
  1745. ######################################################################
  1746. sub HMCCU_FormatReadingValue ($$$)
  1747. {
  1748. my ($hash, $value, $dpt) = @_;
  1749. my $stripnumber = AttrVal ($hash->{NAME}, 'stripnumber', '0');
  1750. return $value if ($stripnumber eq '0' || $value !~ /\.[0-9]+$/);
  1751. foreach my $sr (split (';', $stripnumber)) {
  1752. my ($d, $s) = split ('!', $sr);
  1753. if (defined ($s)) {
  1754. next if ($d eq '' || $dpt !~ /$d/);
  1755. }
  1756. else {
  1757. $s = $sr;
  1758. }
  1759. if ($s eq '1') {
  1760. return sprintf ("%.1f", $value);
  1761. }
  1762. elsif ($s eq '2') {
  1763. return sprintf ("%g", $value);
  1764. }
  1765. elsif ($s =~ /^-([0-9])$/) {
  1766. my $fmt = '%.'.$1.'f';
  1767. return sprintf ($fmt, $value);
  1768. }
  1769. }
  1770. return $value;
  1771. }
  1772. ######################################################################
  1773. # Log message if trace flag is set.
  1774. # Will output multiple log file entries if parameter msg is separated
  1775. # by <br>
  1776. ######################################################################
  1777. sub HMCCU_Trace ($$$$)
  1778. {
  1779. my ($hash, $level, $fnc, $msg) = @_;
  1780. my $name = $hash->{NAME};
  1781. my $type = $hash->{TYPE};
  1782. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  1783. return if ($ccuflags !~ /trace/);
  1784. foreach my $m (split ("<br>", $msg)) {
  1785. $m = "$fnc: $m" if (defined ($fnc) && $fnc ne '');
  1786. Log3 $name, $level, "$type: $m";
  1787. }
  1788. }
  1789. ######################################################################
  1790. # Log message and return code.
  1791. ######################################################################
  1792. sub HMCCU_Log ($$$$)
  1793. {
  1794. my ($hash, $level, $msg, $rc) = @_;
  1795. my $name = $hash->{NAME};
  1796. my $type = $hash->{TYPE};
  1797. Log3 $name, $level, "$type: $msg";
  1798. return $rc;
  1799. }
  1800. ######################################################################
  1801. # Set error state and write log file message
  1802. # Parameter text can be an error code (integer < 0) or an error text.
  1803. # Parameter addinfo is optional.
  1804. ######################################################################
  1805. sub HMCCU_SetError ($@)
  1806. {
  1807. my ($hash, $text, $addinfo) = @_;
  1808. my $name = $hash->{NAME};
  1809. my $type = $hash->{TYPE};
  1810. my $msg;
  1811. my %errlist = (
  1812. -1 => 'Invalid device/channel name or address',
  1813. -2 => 'Execution of CCU script or command failed',
  1814. -3 => 'Cannot detect IO device',
  1815. -4 => 'Device deleted in CCU',
  1816. -5 => 'No response from CCU',
  1817. -6 => 'Update of readings disabled. Set attribute ccureadings first',
  1818. -7 => 'Invalid channel number',
  1819. -8 => 'Invalid datapoint',
  1820. -9 => 'Interface does not support RPC calls',
  1821. -10 => 'No readable datapoints found',
  1822. -11 => 'No state channel defined',
  1823. -12 => 'No control channel defined',
  1824. -13 => 'No state datapoint defined',
  1825. -14 => 'No control datapoint defined',
  1826. -15 => 'No state values defined',
  1827. -16 => 'Cannot open file',
  1828. -17 => 'Cannot detect or create external RPC device',
  1829. -18 => 'Type of system variable not supported'
  1830. );
  1831. $msg = exists ($errlist{$text}) ? $errlist{$text} : $text;
  1832. $msg = $type.": ".$name." ". $msg;
  1833. if (defined ($addinfo) && $addinfo ne '') {
  1834. $msg .= ". $addinfo";
  1835. }
  1836. Log3 $name, 1, $msg;
  1837. return HMCCU_SetState ($hash, "Error", $msg);
  1838. }
  1839. ##################################################################
  1840. # Set state of device if attribute ccuackstate = 1
  1841. ##################################################################
  1842. sub HMCCU_SetState ($@)
  1843. {
  1844. my ($hash, $text, $retval) = @_;
  1845. my $name = $hash->{NAME};
  1846. my $defackstate = $hash->{TYPE} eq 'HMCCU' ? 1 : 0;
  1847. my $ackstate = AttrVal ($name, 'ccuackstate', $defackstate);
  1848. return undef if ($ackstate == 0);
  1849. if (defined ($hash) && defined ($text)) {
  1850. readingsSingleUpdate ($hash, "state", $text, 1);
  1851. }
  1852. return ($text eq "busy") ? "HMCCU: CCU busy" : $retval;
  1853. }
  1854. ######################################################################
  1855. # Substitute first occurrence of regular expression or fixed string.
  1856. # Floating point values are ignored without datapoint specification.
  1857. # Integer values are compared with complete value.
  1858. # mode: 0=Substitute regular expression, 1=Substitute text
  1859. ######################################################################
  1860. sub HMCCU_Substitute ($$$$$)
  1861. {
  1862. my ($value, $substrule, $mode, $chn, $dpt, $std) = @_;
  1863. my $rc = 0;
  1864. my $newvalue;
  1865. return $value if (!defined ($substrule) || $substrule eq '');
  1866. # Remove channel number from datapoint if specified
  1867. if ($dpt =~ /^([0-9]{1,2})\.(.+)$/) {
  1868. ($chn, $dpt) = ($1, $2);
  1869. }
  1870. my @rulelist = split (';', $substrule);
  1871. foreach my $rule (@rulelist) {
  1872. my @ruletoks = split ('!', $rule);
  1873. if (@ruletoks == 2 && $dpt ne '' && $mode == 0) {
  1874. my @dptlist = split (',', $ruletoks[0]);
  1875. foreach my $d (@dptlist) {
  1876. my $c = -1;
  1877. if ($d =~ /^([0-9]{1,2})\.(.+)$/) {
  1878. ($c, $d) = ($1, $2);
  1879. }
  1880. if ($d eq $dpt && ($c == -1 || !defined($chn) || $c == $chn)) {
  1881. ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[1], $mode);
  1882. return $newvalue;
  1883. }
  1884. }
  1885. }
  1886. elsif (@ruletoks == 1) {
  1887. return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/);
  1888. ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[0], $mode);
  1889. return $newvalue if ($rc == 1);
  1890. }
  1891. }
  1892. return $value;
  1893. }
  1894. ######################################################################
  1895. # Execute substitution list.
  1896. # Syntax for single substitution: {#n-n|regexp|text}:newtext
  1897. # mode=0: Substitute regular expression
  1898. # mode=1: Substitute text (for setting statevals)
  1899. # newtext can contain ':'. Parameter ${value} in newtext is
  1900. # substituted by original value.
  1901. # Return (status, value)
  1902. # status=1: value = substituted value
  1903. # status=0: value = original value
  1904. ######################################################################
  1905. sub HMCCU_SubstRule ($$$)
  1906. {
  1907. my ($value, $substitutes, $mode ) = @_;
  1908. my $rc = 0;
  1909. $substitutes =~ s/\$\{value\}/$value/g;
  1910. my @sub_list = split /,/,$substitutes;
  1911. foreach my $s (@sub_list) {
  1912. my ($regexp, $text) = split /:/,$s,2;
  1913. next if (!defined ($regexp) || !defined($text));
  1914. if ($regexp =~ /^#([+-]?\d*\.?\d+?)\-([+-]?\d*\.?\d+?)$/) {
  1915. my ($mi, $ma) = ($1, $2);
  1916. if ($value =~ /^\d*\.?\d+?$/ && $value >= $mi && $value <= $ma) {
  1917. $value = $text;
  1918. $rc = 1;
  1919. }
  1920. }
  1921. if ($mode == 0 && $value =~ /$regexp/ && $value !~ /^[+-]?\d+$/) {
  1922. my $x = eval { $value =~ s/$regexp/$text/ };
  1923. $rc = 1 if (defined ($x));
  1924. last;
  1925. }
  1926. elsif (($mode == 1 || $value =~/^[+-]?\d+$/) && $value =~ /^$regexp$/) {
  1927. my $x = eval { $value =~ s/^$regexp$/$text/ };
  1928. $rc = 1 if (defined ($x));
  1929. last;
  1930. }
  1931. }
  1932. return ($rc, $value);
  1933. }
  1934. ######################################################################
  1935. # Substitute datapoint variables in string by datapoint value. The
  1936. # value depends on the character preceding the variable name. Syntax
  1937. # of variable names is:
  1938. # {$|$$|%|%%}{[cn.]Name}
  1939. # {$|$$|%|%%}[cn.]Name
  1940. # % = Original / raw value
  1941. # %% = Previous original / raw value
  1942. # $ = Converted / formatted value
  1943. # $$ = Previous converted / formatted value
  1944. ######################################################################
  1945. sub HMCCU_SubstVariables ($$$)
  1946. {
  1947. my ($clhash, $text, $dplist) = @_;
  1948. my @varlist;
  1949. if (defined ($dplist)) {
  1950. @varlist = split (',', $dplist);
  1951. }
  1952. else {
  1953. @varlist = keys %{$clhash->{hmccu}{dp}};
  1954. }
  1955. # Substitute datapoint variables by value
  1956. # foreach my $dp (keys %{$clhash->{hmccu}{dp}}) {
  1957. foreach my $dp (@varlist) {
  1958. my ($chn, $dpt) = split (/\./, $dp);
  1959. if (defined ($clhash->{hmccu}{dp}{$dp}{VAL})) {
  1960. # my $value = HMCCU_FormatReadingValue ($clhash, $clhash->{hmccu}{dp}{$dp}{VAL});
  1961. # $text =~ s/\$\{$dp\}/$value/g;
  1962. # $text =~ s/\$\{$dpt\}/$value/g;
  1963. $text =~ s/\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g;
  1964. $text =~ s/\$\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g;
  1965. }
  1966. if (defined ($clhash->{hmccu}{dp}{$dp}{OVAL})) {
  1967. $text =~ s/\$\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{OVAL}/g;
  1968. $text =~ s/\$\$\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{OVAL}/g;
  1969. }
  1970. if (defined ($clhash->{hmccu}{dp}{$dp}{SVAL})) {
  1971. $text =~ s/\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{SVAL}/g;
  1972. $text =~ s/\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{SVAL}/g;
  1973. }
  1974. if (defined ($clhash->{hmccu}{dp}{$dp}{OSVAL})) {
  1975. $text =~ s/\%\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g;
  1976. $text =~ s/\%\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g;
  1977. }
  1978. }
  1979. return $text;
  1980. }
  1981. ######################################################################
  1982. # Update all datapoint/readings of all client devices matching
  1983. # specified regular expression. Update will fail if device is deleted
  1984. # or disabled or if attribute ccureadings of a device is set to 0.
  1985. # If fromccu is 1 regular expression is compared to CCU device name.
  1986. # Otherwise it's compared to FHEM device name.
  1987. ######################################################################
  1988. sub HMCCU_UpdateClients ($$$$)
  1989. {
  1990. my ($hash, $devexp, $ccuget, $fromccu) = @_;
  1991. my $fhname = $hash->{NAME};
  1992. my $c_ok = 0;
  1993. my $c_err = 0;
  1994. if ($fromccu) {
  1995. foreach my $name (sort keys %{$hash->{hmccu}{adr}}) {
  1996. next if ($name !~ /$devexp/ || !($hash->{hmccu}{adr}{$name}{valid}));
  1997. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef,
  1998. "ccudevstate=active");
  1999. foreach my $d (@devlist) {
  2000. my $ch = $defs{$d};
  2001. next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
  2002. next if ($ch->{ccuaddr} ne $hash->{hmccu}{adr}{$name}{address});
  2003. my $rc = HMCCU_GetUpdate ($ch, $hash->{hmccu}{adr}{$name}{address}, $ccuget);
  2004. if ($rc <= 0) {
  2005. if ($rc == -10) {
  2006. Log3 $fhname, 3, "HMCCU: Device $name has no readable datapoints";
  2007. }
  2008. else {
  2009. Log3 $fhname, 2, "HMCCU: Update of device $name failed" if ($rc != -10);
  2010. }
  2011. $c_err++;
  2012. }
  2013. else {
  2014. $c_ok++;
  2015. }
  2016. }
  2017. }
  2018. }
  2019. else {
  2020. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", $devexp,
  2021. "ccudevstate=active");
  2022. Log3 $fhname, 2, "HMCCU: No client devices matching $devexp" if (scalar (@devlist) == 0);
  2023. foreach my $d (@devlist) {
  2024. my $ch = $defs{$d};
  2025. next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
  2026. my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget);
  2027. if ($rc <= 0) {
  2028. if ($rc == -10) {
  2029. Log3 $fhname, 3, "HMCCU: Device ".$ch->{ccuaddr}." has no readable datapoints";
  2030. }
  2031. else {
  2032. Log3 $fhname, 2, "HMCCU: Update of device ".$ch->{ccuaddr}." failed"
  2033. if ($ch->{ccuif} ne 'VirtualDevices');
  2034. }
  2035. $c_err++;
  2036. }
  2037. else {
  2038. $c_ok++;
  2039. }
  2040. }
  2041. }
  2042. return ($c_ok, $c_err);
  2043. }
  2044. ##########################################################################
  2045. # Update parameters in internal device tables and client devices.
  2046. # Parameter devices is a hash reference with following keys:
  2047. # {address}
  2048. # {address}{flag} := [N, D, R]
  2049. # {address}{addtype} := [chn, dev]
  2050. # {address}{channels} := Number of channels
  2051. # {address}{name} := Device or channel name
  2052. # {address}{type} := Homematic device type
  2053. # {address}{usetype} := Usage type
  2054. # {address}{interface} := Device interface ID
  2055. # {address}{firmware} := Firmware version of device
  2056. # {address}{version} := Version of RPC device description
  2057. # {address}{rxmode} := Transmit mode
  2058. # {address}{chndir} := Channel direction: 0=none, 1=sensor, 2=actor
  2059. # If flag is 'D' the hash must contain an entry for the device address
  2060. # and for each channel address.
  2061. ##########################################################################
  2062. sub HMCCU_UpdateDeviceTable ($$)
  2063. {
  2064. my ($hash, $devices) = @_;
  2065. my $name = $hash->{NAME};
  2066. my $devcount = 0;
  2067. my $chncount = 0;
  2068. # Update internal device table
  2069. foreach my $da (keys %{$devices}) {
  2070. my $nm = $hash->{hmccu}{dev}{$da}{name} if (defined ($hash->{hmccu}{dev}{$da}{name}));
  2071. $nm = $devices->{$da}{name} if (defined ($devices->{$da}{name}));
  2072. if ($devices->{$da}{flag} eq 'N' && defined ($nm)) {
  2073. my $at = HMCCU_IsChnAddr ($da, 0) ? 'chn' : 'dev';
  2074. Log3 $name, 2, "HMCCU: Duplicate name for device/channel $nm address=$da in CCU."
  2075. if (exists ($hash->{hmccu}{adr}{$nm}) && $at ne $hash->{hmccu}{adr}{$nm}{addtype});
  2076. # Updated or new device/channel
  2077. $hash->{hmccu}{dev}{$da}{addtype} = $at;
  2078. $hash->{hmccu}{dev}{$da}{name} = $nm if (defined ($nm));
  2079. $hash->{hmccu}{dev}{$da}{valid} = 1;
  2080. $hash->{hmccu}{dev}{$da}{channels} = $devices->{$da}{channels}
  2081. if (defined ($devices->{$da}{channels}));
  2082. $hash->{hmccu}{dev}{$da}{type} = $devices->{$da}{type}
  2083. if (defined ($devices->{$da}{type}));
  2084. $hash->{hmccu}{dev}{$da}{usetype} = $devices->{$da}{usetype}
  2085. if (defined ($devices->{$da}{usetype}));
  2086. $hash->{hmccu}{dev}{$da}{interface} = $devices->{$da}{interface}
  2087. if (defined ($devices->{$da}{interface}));
  2088. $hash->{hmccu}{dev}{$da}{version} = $devices->{$da}{version}
  2089. if (defined ($devices->{$da}{version}));
  2090. $hash->{hmccu}{dev}{$da}{firmware} = $devices->{$da}{firmware}
  2091. if (defined ($devices->{$da}{firmware}));
  2092. $hash->{hmccu}{dev}{$da}{rxmode} = $devices->{$da}{rxmode}
  2093. if (defined ($devices->{$da}{rxmode}));
  2094. $hash->{hmccu}{dev}{$da}{chndir} = $devices->{$da}{chndir}
  2095. if (defined ($devices->{$da}{chndir}));
  2096. $hash->{hmccu}{adr}{$nm}{address} = $da;
  2097. $hash->{hmccu}{adr}{$nm}{addtype} = $hash->{hmccu}{dev}{$da}{addtype};
  2098. $hash->{hmccu}{adr}{$nm}{valid} = 1 if (defined ($nm));
  2099. }
  2100. elsif ($devices->{$da}{flag} eq 'D' && exists ($hash->{hmccu}{dev}{$da})) {
  2101. # Device deleted, mark as invalid
  2102. $hash->{hmccu}{dev}{$da}{valid} = 0;
  2103. $hash->{hmccu}{adr}{$nm}{valid} = 0 if (defined ($nm));
  2104. }
  2105. elsif ($devices->{$da}{flag} eq 'R' && exists ($hash->{hmccu}{dev}{$da})) {
  2106. # Device replaced, change address
  2107. my $na = $devices->{hmccu}{newaddr};
  2108. # Copy device entries and delete old device entries
  2109. foreach my $k (keys %{$hash->{hmccu}{dev}{$da}}) {
  2110. $hash->{hmccu}{dev}{$na}{$k} = $hash->{hmccu}{dev}{$da}{$k};
  2111. }
  2112. $hash->{hmccu}{adr}{$nm}{address} = $na;
  2113. delete $hash->{hmccu}{dev}{$da};
  2114. }
  2115. }
  2116. # Update client devices
  2117. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  2118. foreach my $d (@devlist) {
  2119. my $ch = $defs{$d};
  2120. my $ct = $ch->{TYPE};
  2121. my $ca = $ch->{ccuaddr};
  2122. next if (!exists ($devices->{$ca}));
  2123. if ($devices->{$ca}{flag} eq 'N') {
  2124. # New device or new device information
  2125. $ch->{ccudevstate} = 'active';
  2126. if ($ct eq 'HMCCUDEV') {
  2127. $ch->{ccutype} = $hash->{hmccu}{dev}{$ca}{type}
  2128. if (defined ($hash->{hmccu}{dev}{$ca}{type}));
  2129. $ch->{firmware} = $devices->{$ca}{firmware}
  2130. if (defined ($devices->{$ca}{firmware}));
  2131. }
  2132. else {
  2133. $ch->{chntype} = $devices->{$ca}{usetype}
  2134. if (defined ($devices->{$ca}{usetype}));
  2135. my ($add, $chn) = HMCCU_SplitChnAddr ($ca);
  2136. $ch->{ccutype} = $devices->{$add}{type}
  2137. if (defined ($devices->{$add}{type}));
  2138. $ch->{firmware} = $devices->{$add}{firmware}
  2139. if (defined ($devices->{$add}{firmware}));
  2140. }
  2141. $ch->{ccuname} = $hash->{hmccu}{dev}{$ca}{name}
  2142. if (defined ($hash->{hmccu}{dev}{$ca}{name}));
  2143. $ch->{ccuif} = $hash->{hmccu}{dev}{$ca}{interface}
  2144. if (defined ($devices->{$ca}{interface}));
  2145. $ch->{channels} = $hash->{hmccu}{dev}{$ca}{channels}
  2146. if (defined ($hash->{hmccu}{dev}{$ca}{channels}));
  2147. }
  2148. elsif ($devices->{$ca}{flag} eq 'D') {
  2149. # Deleted device
  2150. $ch->{ccudevstate} = 'deleted';
  2151. }
  2152. elsif ($devices->{$ca}{flag} eq 'R') {
  2153. # Replaced device
  2154. $ch->{ccuaddr} = $devices->{$ca}{newaddr};
  2155. }
  2156. }
  2157. # Update internals of I/O device
  2158. foreach my $adr (keys %{$hash->{hmccu}{dev}}) {
  2159. if (exists ($hash->{hmccu}{dev}{$adr}{addtype})) {
  2160. $devcount++ if ($hash->{hmccu}{dev}{$adr}{addtype} eq 'dev');
  2161. $chncount++ if ($hash->{hmccu}{dev}{$adr}{addtype} eq 'chn');
  2162. }
  2163. }
  2164. $hash->{ccudevices} = $devcount;
  2165. $hash->{ccuchannels} = $chncount;
  2166. return ($devcount, $chncount);
  2167. }
  2168. ######################################################################
  2169. # Update a single client device datapoint considering
  2170. # scaling, reading format and value substitution.
  2171. # Return stored value.
  2172. ######################################################################
  2173. sub HMCCU_UpdateSingleDatapoint ($$$$)
  2174. {
  2175. my ($hash, $chn, $dpt, $value) = @_;
  2176. my $hmccu_hash = HMCCU_GetHash ($hash);
  2177. return $value if (!defined ($hmccu_hash));
  2178. my %objects;
  2179. my $ccuaddr = $hash->{ccuaddr};
  2180. my ($devaddr, $chnnum) = HMCCU_SplitChnAddr ($ccuaddr);
  2181. $objects{$devaddr}{$chn}{$dpt} = $value;
  2182. my $rc = HMCCU_UpdateSingleDevice ($hmccu_hash, $hash, \%objects);
  2183. return (ref ($rc)) ? $rc->{$devaddr}{$chn}{$dpt} : $value;
  2184. }
  2185. ######################################################################
  2186. # Update readings of client device.
  2187. # Parameter objects is a hash reference which contains updated data
  2188. # for any device:
  2189. # {devaddr}
  2190. # {devaddr}{channelno}
  2191. # {devaddr}{channelno}{datapoint}
  2192. # {devaddr}{channelno}{datapoint} = value
  2193. # If client device is virtual device group: check if group members are
  2194. # affected by updates and update readings in virtual group device.
  2195. # Return a hash reference with datapoints and new values:
  2196. # {devaddr}
  2197. # {devaddr}{datapoint} = value
  2198. ######################################################################
  2199. sub HMCCU_UpdateSingleDevice ($$$)
  2200. {
  2201. my ($ccuhash, $clthash, $objects) = @_;
  2202. my $ccuname = $ccuhash->{NAME};
  2203. my $cltname = $clthash->{NAME};
  2204. my $clttype = $clthash->{TYPE};
  2205. my $fnc = "UpdateSingleDevice";
  2206. return 0 if (!defined ($clthash->{IODev}) || !defined ($clthash->{ccuaddr}));
  2207. return 0 if ($clthash->{IODev} != $ccuhash);
  2208. # Check for updated data
  2209. my ($devaddr, $cnum) = HMCCU_SplitChnAddr ($clthash->{ccuaddr});
  2210. return 0 if (!exists ($objects->{$devaddr}));
  2211. return 0 if ($clttype eq 'HMCUCCHN' && !exists ($objects->{$devaddr}{$cnum}) &&
  2212. !exists ($objects->{$devaddr}{0}));
  2213. # Get attributes of IO device
  2214. my $ccuflags = AttrVal ($ccuname, 'ccuflags', 'null');
  2215. # Get attributes of client device
  2216. # my $cltflags = AttrVal ($cltname, 'ccuflags', 'null');
  2217. # Build device list including virtual devices
  2218. my @grplist = ($cltname);
  2219. my @virlist = HMCCU_FindClientDevices ($ccuhash, "HMCCUDEV", undef, "ccuif=VirtualDevices");
  2220. foreach my $vd (@virlist) {
  2221. my $vh = $defs{$vd};
  2222. next if (!defined ($vh->{ccugroup}));
  2223. foreach my $gadd (split (",", $vh->{ccugroup})) {
  2224. if ("$gadd" eq "$devaddr") {
  2225. push @grplist, $vd;
  2226. last;
  2227. }
  2228. }
  2229. }
  2230. HMCCU_Trace ($clthash, 2, $fnc,
  2231. "$cltname Virlist = ".join(',', @virlist)."<br>".
  2232. "$cltname Grplist = ".join(',', @grplist)."<br>".
  2233. "$cltname Objects = ".join(',', keys %{$objects}));
  2234. # Store the resulting readings
  2235. my %results;
  2236. # Update device considering foreign device data assigned to group device
  2237. foreach my $cn (@grplist) {
  2238. my $ch = $defs{$cn};
  2239. my $ct = $ch->{TYPE};
  2240. my $disable = AttrVal ($cn, 'disable', 0);
  2241. my $update = AttrVal ($cn, 'ccureadings', 1);
  2242. next if ($update == 0 || $disable == 1);
  2243. my $cf = AttrVal ($cn, 'ccuflags', 'null');
  2244. my $peer = AttrVal ($cn, 'peer', 'null');
  2245. HMCCU_Trace ($ch, 2, $fnc, "Processing device $cn");
  2246. my $crf = HMCCU_GetAttrReadingFormat ($ch, $ccuhash);
  2247. my $substitute = HMCCU_GetAttrSubstitute ($ch, $ccuhash);
  2248. my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($ch, '', 'STATE', '', '');
  2249. my @devlist = ($ch->{ccuaddr});
  2250. push @devlist, split (",", $ch->{ccugroup})
  2251. if ($ch->{ccuif} eq 'VirtualDevices' && exists ($ch->{ccugroup}));
  2252. readingsBeginUpdate ($ch);
  2253. foreach my $dev (@devlist) {
  2254. my ($da, $cnum) = HMCCU_SplitChnAddr ($dev);
  2255. next if (!exists ($objects->{$da}));
  2256. next if ($clttype eq 'HMCUCCHN' && !exists ($objects->{$da}{$cnum}) &&
  2257. !exists ($objects->{$da}{0}));
  2258. # Update channels of device
  2259. foreach my $chnnum (keys (%{$objects->{$da}})) {
  2260. next if ($ct eq 'HMCCUCHN' && "$chnnum" ne "$cnum" && "$chnnum" ne "0");
  2261. next if ("$chnnum" eq "0" && $cf =~ /nochn0/);
  2262. my $chnadd = "$da:$chnnum";
  2263. # Update datapoints of channel
  2264. foreach my $dpt (keys (%{$objects->{$da}{$chnnum}})) {
  2265. my $value = $objects->{$da}{$chnnum}{$dpt};
  2266. next if (!defined ($value));
  2267. # Store datapoint raw value in device hash
  2268. if (exists ($clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL})) {
  2269. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OVAL} = $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL};
  2270. }
  2271. else {
  2272. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OVAL} = $value;
  2273. }
  2274. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{VAL} = $value;
  2275. HMCCU_Trace ($ch, 2, $fnc, "dev=$cn, chnadd=$chnadd, dpt=$dpt, value=$value");
  2276. if (HMCCU_FilterReading ($ch, $chnadd, $dpt)) {
  2277. my @readings = HMCCU_GetReadingName ($ch, '', $da, $chnnum, $dpt, '', $crf);
  2278. my $svalue = HMCCU_ScaleValue ($ch, $dpt, $value, 0);
  2279. my $fvalue = HMCCU_FormatReadingValue ($ch, $svalue, $dpt);
  2280. my $cvalue = HMCCU_Substitute ($fvalue, $substitute, 0, $chnnum, $dpt);
  2281. my %calcs = HMCCU_CalculateReading ($ch, $chnnum, $dpt);
  2282. # Store the resulting value after scaling, formatting and substitution
  2283. if (exists ($clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL})) {
  2284. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL} = $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{SVAL};
  2285. }
  2286. else {
  2287. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{OSVAL} = $cvalue;
  2288. }
  2289. $clthash->{hmccu}{dp}{"$chnnum.$dpt"}{SVAL} = $cvalue;
  2290. $results{$da}{$chnnum}{$dpt} = $cvalue;
  2291. HMCCU_Trace ($ch, 2, $fnc,
  2292. "device=$cltname, readings=".join(',', @readings).
  2293. ", orgvalue=$value value=$cvalue peer=$peer");
  2294. # Update readings
  2295. foreach my $rn (@readings) {
  2296. HMCCU_BulkUpdate ($ch, $rn, $fvalue, $cvalue) if ($rn ne '');
  2297. }
  2298. foreach my $clcr (keys %calcs) {
  2299. HMCCU_BulkUpdate ($ch, $clcr, $calcs{$clcr}, $calcs{$clcr});
  2300. }
  2301. HMCCU_BulkUpdate ($ch, 'control', $fvalue, $cvalue)
  2302. if ($cd ne '' && $dpt eq $cd && $chnnum eq $cc);
  2303. HMCCU_BulkUpdate ($ch, 'state', $fvalue, $cvalue)
  2304. if ($dpt eq $st && ($sc eq '' || $sc eq $chnnum));
  2305. # Update peers
  2306. HMCCU_UpdatePeers ($ch, "$chnnum.$dpt", $cvalue, $peer) if ($peer ne 'null');
  2307. }
  2308. }
  2309. }
  2310. }
  2311. # Calculate and update HomeMatic state
  2312. if ($ccuflags !~ /nohmstate/) {
  2313. my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($cn, $ccuname, undef);
  2314. HMCCU_BulkUpdate ($ch, $hms_read, $hms_val, $hms_val) if (defined ($hms_val));
  2315. }
  2316. readingsEndUpdate ($ch, 1);
  2317. }
  2318. return \%results;
  2319. }
  2320. ######################################################################
  2321. # Update readings of multiple client devices.
  2322. # Parameter objects is a hash reference:
  2323. # {devaddr}
  2324. # {devaddr}{channelno}
  2325. # {devaddr}{channelno}{datapoint} = value
  2326. # Return number of updated devices.
  2327. ######################################################################
  2328. sub HMCCU_UpdateMultipleDevices ($$)
  2329. {
  2330. my ($hash, $objects) = @_;
  2331. my $name = $hash->{NAME};
  2332. my $fnc = "UpdateMultipleDevices";
  2333. my $c = 0;
  2334. # Check syntax
  2335. return 0 if (!defined ($hash) || !defined ($objects));
  2336. # Update reading in matching client devices
  2337. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef,
  2338. "ccudevstate=active");
  2339. foreach my $d (@devlist) {
  2340. my $ch = $defs{$d};
  2341. my $rc = HMCCU_UpdateSingleDevice ($hash, $ch, $objects);
  2342. $c++ if (ref ($rc));
  2343. }
  2344. return $c;
  2345. }
  2346. ######################################################################
  2347. # Update peer devices.
  2348. # Syntax of peer definitions is:
  2349. # channel.datapoint[,...]:condition:type:action
  2350. # condition := valid perl expression. Any channel.datapoint
  2351. # combination is substituted by the corresponding value. If channel
  2352. # is preceded by a % it's substituted by the raw value. If it's
  2353. # preceded by a $ it's substituted by the formated/converted value.
  2354. # If % or $ is doubled the old values are used.
  2355. # type := type of action. Valid types are ccu, hmccu and fhem.
  2356. # action := Action to be performed if result of condition is true.
  2357. # Depending on type action type this could be an assignment or a
  2358. # FHEM command. If action contains $value this parameter is
  2359. # substituted by the original value of the datapoint which has
  2360. # triggered the action.
  2361. # assignment := channel.datapoint=expression
  2362. ######################################################################
  2363. sub HMCCU_UpdatePeers ($$$$)
  2364. {
  2365. my ($clt_hash, $chndpt, $val, $peerattr) = @_;
  2366. my $fnc = "UpdatePeers";
  2367. HMCCU_Trace ($clt_hash, 2, $fnc, "chndpt=$chndpt val=$val peer=$peerattr");
  2368. my @rules = split (/[;\n]+/, $peerattr);
  2369. foreach my $r (@rules) {
  2370. HMCCU_Trace ($clt_hash, 2, $fnc, "rule=$r");
  2371. my ($vars, $cond, $type, $act) = split (/:/, $r, 4);
  2372. next if (!defined ($act));
  2373. HMCCU_Trace ($clt_hash, 2, $fnc, "vars=$vars, cond=$cond, type=$type, act=$act");
  2374. next if ($cond !~ /$chndpt/);
  2375. HMCCU_Trace ($clt_hash, 2, $fnc, "eval $cond");
  2376. # Check if rule is affected by datapoint update
  2377. my $ex = 0;
  2378. foreach my $dpt (split (",", $vars)) {
  2379. HMCCU_Trace ($clt_hash, 2, $fnc, "dpt=$dpt");
  2380. $ex = 1 if ($ex == 0 && $dpt eq $chndpt);
  2381. if (!exists ($clt_hash->{hmccu}{dp}{$dpt})) {
  2382. HMCCU_Trace ($clt_hash, 2, $fnc, "Datapoint $dpt does not exist on hash");
  2383. }
  2384. last if ($ex == 1);
  2385. }
  2386. next if (! $ex);
  2387. # Substitute variables and evaluate condition
  2388. $cond = HMCCU_SubstVariables ($clt_hash, $cond, $vars);
  2389. my $e = eval "$cond";
  2390. HMCCU_Trace ($clt_hash, 2, $fnc, "Error in eval $cond") if (!defined ($e));
  2391. HMCCU_Trace ($clt_hash, 2, $fnc, "NoMatch in eval $cond") if (defined ($e) && $e eq '');
  2392. next if (!defined ($e) || $e eq '');
  2393. # Substitute variables and execute action
  2394. if ($type eq 'ccu' || $type eq 'hmccu') {
  2395. my ($aobj, $aexp) = split (/=/, $act);
  2396. $aexp =~ s/\$value/$val/g;
  2397. $aexp = HMCCU_SubstVariables ($clt_hash, $aexp, $vars);
  2398. HMCCU_Trace ($clt_hash, 2, $fnc, "set $aobj to $aexp");
  2399. HMCCU_SetDatapoint ($clt_hash, "$type:$aobj", $aexp);
  2400. }
  2401. elsif ($type eq 'fhem') {
  2402. $act =~ s/\$value/$val/g;
  2403. $act = HMCCU_SubstVariables ($clt_hash, $act, $vars);
  2404. HMCCU_Trace ($clt_hash, 2, $fnc, "Execute command $act");
  2405. AnalyzeCommandChain (undef, $act);
  2406. }
  2407. }
  2408. }
  2409. ######################################################################
  2410. # Get list of valid RPC ports.
  2411. # Considers binary RPC ports and interfaces used by CCU devices.
  2412. # Default is 2001.
  2413. ######################################################################
  2414. sub HMCCU_GetRPCPortList ($)
  2415. {
  2416. my ($hash) = @_;
  2417. my $name = $hash->{NAME};
  2418. my @ports = ($HMCCU_RPC_PORT_DEFAULT);
  2419. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  2420. if (defined ($hash->{hmccu}{rpcports})) {
  2421. foreach my $p (split (',', $hash->{hmccu}{rpcports})) {
  2422. my $ifname = $HMCCU_RPC_NUMPORT{$p};
  2423. next if ($p == $HMCCU_RPC_PORT_DEFAULT ||
  2424. ($HMCCU_RPC_PROT{$p} eq 'B' && $ccuflags !~ /extrpc/) ||
  2425. !exists ($hash->{hmccu}{iface}{$ifname}));
  2426. push (@ports, $p);
  2427. }
  2428. }
  2429. return @ports;
  2430. }
  2431. ######################################################################
  2432. # Register RPC callbacks at CCU if RPC-Server already in server loop
  2433. ######################################################################
  2434. sub HMCCU_RPCRegisterCallback ($)
  2435. {
  2436. my ($hash) = @_;
  2437. my $name = $hash->{NAME};
  2438. my $serveraddr = $hash->{host};
  2439. my $localaddr = $hash->{hmccu}{localaddr};
  2440. my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL2);
  2441. my $rpcserveraddr = AttrVal ($name, 'rpcserveraddr', $localaddr);
  2442. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  2443. my @rpcports = HMCCU_GetRPCPortList ($hash);
  2444. foreach my $port (@rpcports) {
  2445. my $clkey = 'CB'.$port;
  2446. my $cburl = "http://".$localaddr.":".$hash->{hmccu}{rpc}{$clkey}{cbport}."/fh".$port;
  2447. my $url = "http://$serveraddr:$port/";
  2448. $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port}));
  2449. if ($hash->{hmccu}{rpc}{$clkey}{loop} == 1 ||
  2450. $hash->{hmccu}{rpc}{$clkey}{state} eq "register") {
  2451. $hash->{hmccu}{rpc}{$clkey}{port} = $port;
  2452. $hash->{hmccu}{rpc}{$clkey}{clurl} = $url;
  2453. $hash->{hmccu}{rpc}{$clkey}{cburl} = $cburl;
  2454. $hash->{hmccu}{rpc}{$clkey}{loop} = 2;
  2455. $hash->{hmccu}{rpc}{$clkey}{state} = "registered";
  2456. Log3 $name, 1, "HMCCU: Registering callback $cburl with ID $clkey at $url";
  2457. my $rpcclient = RPC::XML::Client->new ($url);
  2458. $rpcclient->send_request ("init", $cburl, $clkey);
  2459. Log3 $name, 1, "HMCCU: RPC callback with URL $cburl initialized";
  2460. }
  2461. }
  2462. # Schedule reading of RPC queue
  2463. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  2464. }
  2465. ######################################################################
  2466. # Deregister RPC callbacks at CCU
  2467. ######################################################################
  2468. sub HMCCU_RPCDeRegisterCallback ($)
  2469. {
  2470. my ($hash) = @_;
  2471. my $name = $hash->{NAME};
  2472. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  2473. my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  2474. if (exists ($rpchash->{cburl}) && $rpchash->{cburl} ne '') {
  2475. my $port = $rpchash->{port};
  2476. my $rpcclient = RPC::XML::Client->new ($rpchash->{clurl});
  2477. Log3 $name, 1, "HMCCU: Deregistering RPC server ".$rpchash->{cburl}.
  2478. " at ".$rpchash->{clurl};
  2479. $rpcclient->send_request("init", $rpchash->{cburl});
  2480. $rpchash->{cburl} = '';
  2481. $rpchash->{clurl} = '';
  2482. $rpchash->{cbport} = 0;
  2483. }
  2484. }
  2485. }
  2486. ######################################################################
  2487. # Initialize statistic counters
  2488. ######################################################################
  2489. sub HMCCU_ResetCounters ($)
  2490. {
  2491. my ($hash) = @_;
  2492. my @counters = ('total', 'EV', 'ND', 'IN', 'DD', 'RA', 'RD', 'UD', 'EX', 'SL', 'ST');
  2493. foreach my $cnt (@counters) {
  2494. $hash->{hmccu}{ev}{$cnt} = 0;
  2495. }
  2496. delete $hash->{hmccu}{evs};
  2497. delete $hash->{hmccu}{evr};
  2498. $hash->{hmccu}{evtimeout} = 0;
  2499. $hash->{hmccu}{evtime} = 0;
  2500. }
  2501. ######################################################################
  2502. # Start external RPC server via HMCCURPC device.
  2503. # Return number of RPC server threads or 0 on error.
  2504. ######################################################################
  2505. sub HMCCU_StartExtRPCServer ($)
  2506. {
  2507. my ($hash) = @_;
  2508. my $name = $hash->{NAME};
  2509. # Search RPC device. Create one if none exists
  2510. my $rpcdev = HMCCU_GetRPCDevice ($hash, 1);
  2511. return HMCCU_Log ($hash, 0, "Can't find or create HMCCURPC device", 0) if ($rpcdev eq '');
  2512. my ($rc, $msg) = HMCCURPC_StartRPCServer ($defs{$rpcdev});
  2513. Log3 $name, 0, "HMCCURPC: $msg" if (!$rc && defined ($msg));
  2514. return $rc;
  2515. }
  2516. ######################################################################
  2517. # Stop external RPC server via HMCCURPC device.
  2518. ######################################################################
  2519. sub HMCCU_StopExtRPCServer ($)
  2520. {
  2521. my ($hash) = @_;
  2522. my $name = $hash->{NAME};
  2523. return HMCCU_Log ($hash, 0, "Module HMCCURPC not loaded", 0) if (!exists ($modules{'HMCCURPC'}));
  2524. # Search RPC device
  2525. my $rpcdev = HMCCU_GetRPCDevice ($hash, 0);
  2526. return HMCCU_Log ($hash, 0, "Can't find RPC device", 0) if ($rpcdev eq '');
  2527. return HMCCURPC_StopRPCServer ($defs{$rpcdev});
  2528. }
  2529. ######################################################################
  2530. # Start internal file queue based RPC server.
  2531. # Return number of RPC server processes or 0 on error.
  2532. ######################################################################
  2533. sub HMCCU_StartIntRPCServer ($)
  2534. {
  2535. my ($hash) = @_;
  2536. my $name = $hash->{NAME};
  2537. # Timeouts
  2538. my $timeout = AttrVal ($name, 'rpctimeout', '0.01,0.25');
  2539. my ($to_read, $to_write) = split (",", $timeout);
  2540. $to_write = $to_read if (!defined ($to_write));
  2541. # Address and ports
  2542. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  2543. my $rpcserverport = AttrVal ($name, 'rpcserverport', 5400);
  2544. my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL1);
  2545. my @rpcportlist = HMCCU_GetRPCPortList ($hash);
  2546. my $serveraddr = $hash->{host};
  2547. my $fork_cnt = 0;
  2548. # Check for running RPC server processes
  2549. my @hm_pids;
  2550. my @hm_tids;
  2551. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  2552. if (scalar (@hm_pids) > 0) {
  2553. return HMCCU_Log ($hash, 0, "RPC server(s) already running with PIDs ".join (',', @hm_pids),
  2554. scalar (@hm_pids));
  2555. }
  2556. elsif (scalar (@hm_tids) > 0) {
  2557. return HMCCU_Log ($hash, 1, "RPC server(s) already running with TIDs ".join (',', @hm_tids),
  2558. 0);
  2559. }
  2560. # Detect local IP address
  2561. my $socket = IO::Socket::INET->new (PeerAddr => $serveraddr, PeerPort => $rpcportlist[0]);
  2562. return HMCCU_Log ($hash, 1, "Can't connect to CCU port".$rpcportlist[0], 0) if (!$socket);
  2563. my $localaddr = $socket->sockhost ();
  2564. close ($socket);
  2565. my $ccunum = $hash->{CCUNum};
  2566. # Fork child processes
  2567. foreach my $port (@rpcportlist) {
  2568. my $clkey = 'CB'.$port;
  2569. my $rpcqueueport = $rpcqueue."_".$port."_".$ccunum;
  2570. my $callbackport = $rpcserverport+$port+($ccunum*10);
  2571. # Clear event queue
  2572. HMCCU_ResetRPCQueue ($hash, $port);
  2573. # Create child process
  2574. Log3 $name, 2, "HMCCU: Create child process with timeouts $to_read and $to_write";
  2575. my $child = SubProcess->new ({ onRun => \&HMCCU_CCURPC_OnRun,
  2576. onExit => \&HMCCU_CCURPC_OnExit, timeoutread => $to_read, timeoutwrite => $to_write });
  2577. $child->{serveraddr} = $serveraddr;
  2578. $child->{serverport} = $port;
  2579. $child->{callbackport} = $callbackport;
  2580. $child->{devname} = $name;
  2581. $child->{queue} = $rpcqueueport;
  2582. # Start child process
  2583. my $pid = $child->run ();
  2584. if (!defined ($pid)) {
  2585. Log3 $name, 1, "HMCCU: No RPC process for server $clkey started";
  2586. next;
  2587. }
  2588. Log3 $name, 0, "HMCCU: Child process for server $clkey started with PID $pid";
  2589. $fork_cnt++;
  2590. # Store child process parameters
  2591. $hash->{hmccu}{rpc}{$clkey}{child} = $child;
  2592. $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport;
  2593. $hash->{hmccu}{rpc}{$clkey}{loop} = 0;
  2594. $hash->{hmccu}{rpc}{$clkey}{pid} = $pid;
  2595. $hash->{hmccu}{rpc}{$clkey}{queue} = $rpcqueueport;
  2596. $hash->{hmccu}{rpc}{$clkey}{state} = "starting";
  2597. push (@hm_pids, $pid);
  2598. }
  2599. $hash->{hmccu}{rpccount} = $fork_cnt;
  2600. $hash->{hmccu}{localaddr} = $localaddr;
  2601. if ($fork_cnt > 0) {
  2602. # Set internals
  2603. $hash->{RPCPID} = join (',', @hm_pids);
  2604. $hash->{RPCPRC} = "internal";
  2605. $hash->{RPCState} = "starting";
  2606. # Initialize statistic counters
  2607. HMCCU_ResetCounters ($hash);
  2608. readingsSingleUpdate ($hash, "rpcstate", "starting", 1);
  2609. Log3 $name, 0, "RPC server(s) starting";
  2610. DoTrigger ($name, "RPC server starting");
  2611. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  2612. }
  2613. return $fork_cnt;
  2614. }
  2615. ######################################################################
  2616. # Stop RPC server(s) by sending SIGINT to process(es)
  2617. ######################################################################
  2618. sub HMCCU_StopRPCServer ($)
  2619. {
  2620. my ($hash) = @_;
  2621. my $name = $hash->{NAME};
  2622. my $pid = 0;
  2623. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  2624. my $serveraddr = $hash->{host};
  2625. # Deregister callback URLs in CCU
  2626. HMCCU_RPCDeRegisterCallback ($hash);
  2627. # Send signal SIGINT to RPC server processes
  2628. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  2629. my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  2630. if (exists ($rpchash->{pid}) && $rpchash->{pid} != 0) {
  2631. Log3 $name, 0, "HMCCU: Stopping RPC server $clkey with PID ".$rpchash->{pid};
  2632. kill ('INT', $rpchash->{pid});
  2633. $rpchash->{state} = "stopping";
  2634. }
  2635. else {
  2636. $rpchash->{state} = "stopped";
  2637. }
  2638. }
  2639. # Update status
  2640. if ($hash->{hmccu}{rpccount} > 0) {
  2641. readingsSingleUpdate ($hash, "rpcstate", "stopping", 1);
  2642. $hash->{RPCState} = "stopping";
  2643. }
  2644. # Wait
  2645. sleep (1);
  2646. # Check if processes were terminated
  2647. my @hm_pids;
  2648. my @hm_tids;
  2649. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  2650. if (scalar (@hm_pids) > 0) {
  2651. foreach my $pid (@hm_pids) {
  2652. Log3 $name, 0, "HMCCU: Stopping RPC server with PID $pid";
  2653. kill ('INT', $pid);
  2654. }
  2655. }
  2656. Log3 $name, 0, "HMCCU: Externally launched RPC server detected." if (scalar (@hm_tids) > 0);
  2657. # Wait
  2658. sleep (1);
  2659. # Kill the rest
  2660. @hm_pids = ();
  2661. @hm_tids = ();
  2662. if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) {
  2663. foreach my $pid (@hm_pids) {
  2664. kill ('KILL', $pid);
  2665. }
  2666. }
  2667. # Store number of running RPC servers
  2668. $hash->{hmccu}{rpccount} = HMCCU_IsRPCServerRunning ($hash, undef, undef);
  2669. return $hash->{hmccu}{rpccount} > 0 ? 0 : 1;
  2670. }
  2671. ######################################################################
  2672. # Check status of RPC server depending on internal RPCState.
  2673. # Return 1 if RPC server is stopping, starting or restarting. During
  2674. # this phases CCU reacts very slowly so any get or set command from
  2675. # HMCCU devices are disabled.
  2676. ######################################################################
  2677. sub HMCCU_IsRPCStateBlocking ($)
  2678. {
  2679. my ($hash) = @_;
  2680. if ($hash->{RPCState} eq "starting" ||
  2681. $hash->{RPCState} eq "restarting" ||
  2682. $hash->{RPCState} eq "stopping") {
  2683. return 1;
  2684. }
  2685. else {
  2686. return 0;
  2687. }
  2688. }
  2689. ######################################################################
  2690. # Check if RPC servers are running.
  2691. # Return number of running RPC servers. If paramters pids or tids are
  2692. # defined also return process or thread IDs.
  2693. ######################################################################
  2694. sub HMCCU_IsRPCServerRunning ($$$)
  2695. {
  2696. my ($hash, $pids, $tids) = @_;
  2697. my $name = $hash->{NAME};
  2698. my $c = 0;
  2699. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  2700. if ($ccuflags =~ /extrpc/) {
  2701. @$tids = () if (defined ($tids));
  2702. my $rpcdev = HMCCU_GetRPCDevice ($hash, 0);
  2703. if ($rpcdev ne '') {
  2704. my ($r, $a) = HMCCURPC_CheckThreadState ($defs{$rpcdev}, 6, 'running', $tids);
  2705. $c = $r;
  2706. }
  2707. }
  2708. else {
  2709. @$pids = () if (defined ($pids));
  2710. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  2711. if (defined ($hash->{hmccu}{rpc}{$clkey}{pid})) {
  2712. my $pid = $hash->{hmccu}{rpc}{$clkey}{pid};
  2713. if ($pid != 0 && kill (0, $pid)) {
  2714. push (@$pids, $pid) if (defined ($pids));
  2715. $c++;
  2716. }
  2717. }
  2718. }
  2719. }
  2720. return $c;
  2721. }
  2722. ######################################################################
  2723. # Get channels and datapoints of CCU device
  2724. ######################################################################
  2725. sub HMCCU_GetDeviceInfo ($$$)
  2726. {
  2727. my ($hash, $device, $ccuget) = @_;
  2728. my $name = $hash->{NAME};
  2729. my $devname = '';
  2730. my $hmccu_hash = HMCCU_GetHash ($hash);
  2731. return '' if (!defined ($hmccu_hash));
  2732. $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value') if ($ccuget eq 'Attr');
  2733. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $device, 0);
  2734. if ($flags == $HMCCU_FLAG_ADDRESS) {
  2735. $devname = HMCCU_GetDeviceName ($hmccu_hash, $add, '');
  2736. return '' if ($devname eq '');
  2737. }
  2738. else {
  2739. $devname = $nam;
  2740. }
  2741. my $response = HMCCU_HMScriptExt ($hmccu_hash, "!GetDeviceInfo",
  2742. { devname => $devname, ccuget => $ccuget });
  2743. HMCCU_Trace ($hash, 2, undef,
  2744. "Device=$device Devname=$devname<br>".
  2745. "Script response = \n".$response."<br>".
  2746. "Script = GetDeviceInfo");
  2747. return $response;
  2748. }
  2749. ######################################################################
  2750. # Make device info readable
  2751. # n=number, b=bool, f=float, i=integer, s=string, a=alarm, p=presence
  2752. # e=enumeration
  2753. ######################################################################
  2754. sub HMCCU_FormatDeviceInfo ($)
  2755. {
  2756. my ($devinfo) = @_;
  2757. my %vtypes = (0, "n", 2, "b", 4, "f", 6, "a", 8, "n", 11, "s", 16, "i", 20, "s", 23, "p", 29, "e");
  2758. my $result = '';
  2759. my $c_oaddr = '';
  2760. foreach my $dpspec (split ("\n", $devinfo)) {
  2761. my ($c, $c_addr, $c_name, $d_name, $d_type, $d_value, $d_flags) = split (";", $dpspec);
  2762. if ($c_addr ne $c_oaddr) {
  2763. $result .= "CHN $c_addr $c_name\n";
  2764. $c_oaddr = $c_addr;
  2765. }
  2766. my $t = exists ($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type;
  2767. $result .= " DPT {$t} $d_name = $d_value [$d_flags]\n";
  2768. }
  2769. return $result;
  2770. }
  2771. ######################################################################
  2772. # Get available firmware versions from EQ-3 server.
  2773. # Firmware version, date and download link are stored in hash
  2774. # {hmccu}{type}{$type} in elements {firmware}, {date} and {download}.
  2775. # Return number of available firmware downloads.
  2776. ######################################################################
  2777. sub HMCCU_GetFirmwareVersions ($)
  2778. {
  2779. my ($hash) = @_;
  2780. my $name = $hash->{NAME};
  2781. my $url = "http://www.eq-3.de/service/downloads.html";
  2782. my $response = GetFileFromURL ($url, 4, "suchtext=&suche_in=&downloadart=11");
  2783. # my @changebc = $response =~ m/href="(Downloads\/Software\/Firmware\/changelog_[^"]+)/g;
  2784. # my @changeip = $response =~ m/href="(Downloads\/Software\/Firmware\/Homematic IP\/changelog_[^"]+)/g;
  2785. my @download = $response =~ m/<a.href="(Downloads\/Software\/Firmware\/[^"]+)/g;
  2786. my $dc = 0;
  2787. foreach my $dl (@download) {
  2788. my $dd;
  2789. my $mm;
  2790. my $yy;
  2791. my $date = '?';
  2792. my $fw;
  2793. my @path = split (/\//, $dl);
  2794. my $file = pop @path;
  2795. next if ($file !~ /(\.tgz|\.tar\.gz)/);
  2796. # Log3 $name, 2, "HMCCU: $file";
  2797. $file =~ m/^(.+)_update_V([^.]+)/;
  2798. my ($dt, $rest) = ($1, $2);
  2799. $dt =~ s/_/-/g;
  2800. $dt = uc($dt);
  2801. if ($rest =~ /^([\d_]+)([0-9]{2})([0-9]{2})([0-9]{2})$/) {
  2802. ($fw, $yy, $mm, $dd) = ($1, $2, $3, $4);
  2803. $date = "$dd.$mm.20$yy";
  2804. $fw =~ s/_$//;
  2805. }
  2806. else {
  2807. $fw = $rest;
  2808. }
  2809. $fw =~ s/_/\./g;
  2810. $fw =~ s/^V//;
  2811. $dc++;
  2812. $hash->{hmccu}{type}{$dt}{firmware} = $fw;
  2813. $hash->{hmccu}{type}{$dt}{date} = $date;
  2814. $hash->{hmccu}{type}{$dt}{download} = $dl;
  2815. }
  2816. return $dc;
  2817. }
  2818. ######################################################################
  2819. # Read list of CCU devices and channels via Homematic Script.
  2820. # Update data of client devices if not current.
  2821. # Return (device count, channel count) or (-1, -1) on error.
  2822. ######################################################################
  2823. sub HMCCU_GetDeviceList ($)
  2824. {
  2825. my ($hash) = @_;
  2826. my $devcount = 0;
  2827. my $chncount = 0;
  2828. my %objects = ();
  2829. my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList", undef);
  2830. return (-1, -1) if ($response eq '' || $response =~ /^ERROR:.*/);
  2831. # Delete old entries
  2832. %{$hash->{hmccu}{dev}} = ();
  2833. %{$hash->{hmccu}{adr}} = ();
  2834. $hash->{hmccu}{updatetime} = time ();
  2835. # Device hash elements for HMCCU_UpdateDeviceTable():
  2836. #
  2837. # {address}{flag} := [N, D, R]
  2838. # {address}{addtype} := [chn, dev]
  2839. # {address}{channels} := Number of channels
  2840. # {address}{name} := Device or channel name
  2841. # {address}{type} := Homematic device type
  2842. # {address}{usetype} := Usage type
  2843. # {address}{interface} := Device interface ID
  2844. # {address}{firmware} := Firmware version of device
  2845. # {address}{version} := Version of RPC device description
  2846. # {address}{rxmode} := Transmit mode
  2847. # {address}{chndir} := Channel direction: 1=sensor 2=actor 0=none
  2848. my @scrlines = split /\n/,$response;
  2849. foreach my $hmdef (@scrlines) {
  2850. my @hmdata = split /;/,$hmdef;
  2851. next if (scalar (@hmdata) == 0);
  2852. if ($hmdata[0] eq 'D') {
  2853. next if (scalar (@hmdata) != 6);
  2854. # 1=Interface 2=Device-Address 3=Device-Name 4=Device-Type 5=Channel-Count
  2855. $objects{$hmdata[2]}{addtype} = 'dev';
  2856. $objects{$hmdata[2]}{channels} = $hmdata[5];
  2857. $objects{$hmdata[2]}{flag} = 'N';
  2858. $objects{$hmdata[2]}{interface} = $hmdata[1];
  2859. $objects{$hmdata[2]}{name} = $hmdata[3];
  2860. $objects{$hmdata[2]}{type} = ($hmdata[2] =~ /^CUX/) ? "CUX-".$hmdata[4] : $hmdata[4];
  2861. $objects{$hmdata[2]}{chndir} = 0;
  2862. # Count used interfaces
  2863. $hash->{hmccu}{iface}{$hmdata[1]}++;
  2864. # CCU information (address = BidCoS-RF)
  2865. if ($hmdata[2] eq 'BidCoS-RF') {
  2866. $hash->{ccuname} = $hmdata[3];
  2867. $hash->{ccuaddr} = $hmdata[2];
  2868. $hash->{ccuif} = $hmdata[1];
  2869. }
  2870. }
  2871. elsif ($hmdata[0] eq 'C') {
  2872. next if (scalar (@hmdata) != 4);
  2873. # 1=Channel-Address 2=Channel-Name 3=Direction
  2874. $objects{$hmdata[1]}{addtype} = 'chn';
  2875. $objects{$hmdata[1]}{channels} = 1;
  2876. $objects{$hmdata[1]}{flag} = 'N';
  2877. $objects{$hmdata[1]}{name} = $hmdata[2];
  2878. $objects{$hmdata[1]}{valid} = 1;
  2879. $objects{$hmdata[1]}{chndir} = $hmdata[3];
  2880. }
  2881. }
  2882. if (scalar (keys %objects) > 0) {
  2883. # Update some CCU I/O device information
  2884. $hash->{ccuinterfaces} = join (',', keys %{$hash->{hmccu}{iface}});
  2885. # Update HMCCU device tables
  2886. ($devcount, $chncount) = HMCCU_UpdateDeviceTable ($hash, \%objects);
  2887. # Read available datapoints for each device type
  2888. HMCCU_GetDatapointList ($hash);
  2889. }
  2890. return ($devcount, $chncount);
  2891. }
  2892. ######################################################################
  2893. # Read list of datapoints for CCU device types.
  2894. # Function must not be called before GetDeviceList.
  2895. # Return number of datapoints.
  2896. ######################################################################
  2897. sub HMCCU_GetDatapointList ($)
  2898. {
  2899. my ($hash) = @_;
  2900. my $name = $hash->{NAME};
  2901. if (exists ($hash->{hmccu}{dp})) {
  2902. delete $hash->{hmccu}{dp};
  2903. }
  2904. # Select one device for each device type
  2905. my %alltypes;
  2906. my @devunique;
  2907. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  2908. next if ($hash->{hmccu}{dev}{$add}{addtype} ne 'dev');
  2909. my $dt = $hash->{hmccu}{dev}{$add}{type};
  2910. if (defined ($dt)) {
  2911. if ($dt ne '' && !exists ($alltypes{$dt})) {
  2912. $alltypes{$dt} = 1;
  2913. push @devunique, $hash->{hmccu}{dev}{$add}{name};
  2914. }
  2915. }
  2916. else {
  2917. Log3 $name, 2, "HMCCU: Corrupt or invalid entry in device table for device $add";
  2918. }
  2919. }
  2920. if (scalar (@devunique) == 0) {
  2921. Log3 $name, 2, "HMCCU: No device types found in device table. Cannot read datapoints.";
  2922. return 0;
  2923. }
  2924. my $devlist = join (',', @devunique);
  2925. my $response = HMCCU_HMScriptExt ($hash, "!GetDatapointList",
  2926. { list => $devlist });
  2927. if ($response eq '' || $response =~ /^ERROR:.*/) {
  2928. Log3 $name, 2, "HMCCU: Cannot get datapoint list";
  2929. return 0;
  2930. }
  2931. my $c = 0;
  2932. foreach my $dpspec (split /\n/,$response) {
  2933. my ($chna, $devt, $devc, $dptn, $dptt, $dpto) = split (";", $dpspec);
  2934. $devt = "CUX-".$devt if ($chna =~ /^CUX/);
  2935. $hash->{hmccu}{dp}{$devt}{spc}{ontime} = $devc.".".$dptn if ($dptn eq "ON_TIME");
  2936. $hash->{hmccu}{dp}{$devt}{spc}{ramptime} = $devc.".".$dptn if ($dptn eq "RAMP_TIME");
  2937. $hash->{hmccu}{dp}{$devt}{spc}{submit} = $devc.".".$dptn if ($dptn eq "SUBMIT");
  2938. $hash->{hmccu}{dp}{$devt}{spc}{level} = $devc.".".$dptn if ($dptn eq "LEVEL");
  2939. $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{type} = $dptt;
  2940. $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{oper} = $dpto;
  2941. if (exists ($hash->{hmccu}{dp}{$devt}{cnt}{$dptn})) {
  2942. $hash->{hmccu}{dp}{$devt}{cnt}{$dptn}++;
  2943. }
  2944. else {
  2945. $hash->{hmccu}{dp}{$devt}{cnt}{$dptn} = 1;
  2946. }
  2947. $c++;
  2948. }
  2949. return $c;
  2950. }
  2951. ######################################################################
  2952. # Check if device/channel name or address is valid and refers to an
  2953. # existing device or channel.
  2954. ######################################################################
  2955. sub HMCCU_IsValidDeviceOrChannel ($$)
  2956. {
  2957. my ($hash, $param) = @_;
  2958. if (HMCCU_IsDevAddr ($param, 1) || HMCCU_IsChnAddr ($param, 1)) {
  2959. my ($i, $a) = split (/\./, $param);
  2960. return 0 if (! exists ($hash->{hmccu}{dev}{$a}));
  2961. return $hash->{hmccu}{dev}{$a}{valid};
  2962. }
  2963. if (HMCCU_IsDevAddr ($param, 0) || HMCCU_IsChnAddr ($param, 0)) {
  2964. return 0 if (! exists ($hash->{hmccu}{dev}{$param}));
  2965. return $hash->{hmccu}{dev}{$param}{valid};
  2966. }
  2967. else {
  2968. return 0 if (! exists ($hash->{hmccu}{adr}{$param}));
  2969. return $hash->{hmccu}{adr}{$param}{valid};
  2970. }
  2971. }
  2972. ######################################################################
  2973. # Check if device name or address is valid and refers to an existing
  2974. # device.
  2975. ######################################################################
  2976. sub HMCCU_IsValidDevice ($$)
  2977. {
  2978. my ($hash, $param) = @_;
  2979. if (HMCCU_IsDevAddr ($param, 1)) {
  2980. my ($i, $a) = split (/\./, $param);
  2981. return 0 if (! exists ($hash->{hmccu}{dev}{$a}));
  2982. return $hash->{hmccu}{dev}{$a}{valid};
  2983. }
  2984. if (HMCCU_IsDevAddr ($param, 0)) {
  2985. return 0 if (! exists ($hash->{hmccu}{dev}{$param}));
  2986. return $hash->{hmccu}{dev}{$param}{valid};
  2987. }
  2988. else {
  2989. return 0 if (! exists ($hash->{hmccu}{adr}{$param}));
  2990. return $hash->{hmccu}{adr}{$param}{valid} && $hash->{hmccu}{adr}{$param}{addtype} eq 'dev';
  2991. }
  2992. }
  2993. ######################################################################
  2994. # Check if channel name or address is valid and refers to an existing
  2995. # channel.
  2996. ######################################################################
  2997. sub HMCCU_IsValidChannel ($$)
  2998. {
  2999. my ($hash, $param) = @_;
  3000. if (HMCCU_IsChnAddr ($param, 1)) {
  3001. my ($i, $a) = split (/\./, $param);
  3002. return 0 if (! exists ($hash->{hmccu}{dev}{$a}));
  3003. return $hash->{hmccu}{dev}{$a}{valid};
  3004. }
  3005. if (HMCCU_IsChnAddr ($param, 0)) {
  3006. return 0 if (! exists ($hash->{hmccu}{dev}{$param}));
  3007. return $hash->{hmccu}{dev}{$param}{valid};
  3008. }
  3009. else {
  3010. return 0 if (! exists ($hash->{hmccu}{adr}{$param}));
  3011. return $hash->{hmccu}{adr}{$param}{valid} && $hash->{hmccu}{adr}{$param}{addtype} eq 'chn';
  3012. }
  3013. }
  3014. ######################################################################
  3015. # Get CCU parameters of device or channel.
  3016. # Returns list containing interface, deviceaddress, name, type and
  3017. # channels.
  3018. ######################################################################
  3019. sub HMCCU_GetCCUDeviceParam ($$)
  3020. {
  3021. my ($hash, $param) = @_;
  3022. my $name = $hash->{NAME};
  3023. my $devadd;
  3024. my $add = undef;
  3025. my $chn = undef;
  3026. if (HMCCU_IsDevAddr ($param, 1) || HMCCU_IsChnAddr ($param, 1)) {
  3027. my $i;
  3028. ($i, $add) = split (/\./, $param);
  3029. }
  3030. else {
  3031. if (HMCCU_IsDevAddr ($param, 0) || HMCCU_IsChnAddr ($param, 0)) {
  3032. $add = $param;
  3033. }
  3034. else {
  3035. if (exists ($hash->{hmccu}{adr}{$param})) {
  3036. $add = $hash->{hmccu}{adr}{$param}{address};
  3037. }
  3038. }
  3039. }
  3040. return (undef, undef, undef, undef) if (!defined ($add));
  3041. ($devadd, $chn) = HMCCU_SplitChnAddr ($add);
  3042. return (undef, undef, undef, undef) if (!defined ($devadd) ||
  3043. !exists ($hash->{hmccu}{dev}{$devadd}) || $hash->{hmccu}{dev}{$devadd}{valid} == 0);
  3044. return ($hash->{hmccu}{dev}{$devadd}{interface}, $add, $hash->{hmccu}{dev}{$add}{name},
  3045. $hash->{hmccu}{dev}{$devadd}{type}, $hash->{hmccu}{dev}{$add}{channels});
  3046. }
  3047. ######################################################################
  3048. # Get list of valid datapoints for device type.
  3049. # hash = hash of client or IO device
  3050. # devtype = Homematic device type
  3051. # chn = Channel number, -1=all channels
  3052. # oper = Valid operation: 1=Read, 2=Write, 4=Event
  3053. # dplistref = Reference for array with datapoints.
  3054. # Return number of datapoints.
  3055. ######################################################################
  3056. sub HMCCU_GetValidDatapoints ($$$$$)
  3057. {
  3058. my ($hash, $devtype, $chn, $oper, $dplistref) = @_;
  3059. my $hmccu_hash = HMCCU_GetHash ($hash);
  3060. my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null');
  3061. return 0 if ($ccuflags =~ /dptnocheck/);
  3062. return 0 if (!exists ($hmccu_hash->{hmccu}{dp}));
  3063. return HMCCU_Log ($hash, 2, "chn undefined", 0) if (!defined ($chn));
  3064. if ($chn >= 0) {
  3065. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) {
  3066. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  3067. if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dp}{oper} & $oper) {
  3068. push @$dplistref, $dp;
  3069. }
  3070. }
  3071. }
  3072. }
  3073. else {
  3074. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype})) {
  3075. foreach my $ch (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}}) {
  3076. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}}) {
  3077. if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper) {
  3078. push @$dplistref, $ch.".".$dp;
  3079. }
  3080. }
  3081. }
  3082. }
  3083. }
  3084. return scalar (@$dplistref);
  3085. }
  3086. ######################################################################
  3087. # Find a datapoint for device type.
  3088. # hash = hash of client or IO device
  3089. # devtype = Homematic device type
  3090. # chn = Channel number, -1=all channels
  3091. # oper = Valid operation: 1=Read, 2=Write, 4=Event
  3092. # Return channel of first match or -1.
  3093. ######################################################################
  3094. sub HMCCU_FindDatapoint ($$$$$)
  3095. {
  3096. my ($hash, $devtype, $chn, $dpt, $oper) = @_;
  3097. my $hmccu_hash = HMCCU_GetHash ($hash);
  3098. return -1 if (!exists ($hmccu_hash->{hmccu}{dp}));
  3099. if ($chn >= 0) {
  3100. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) {
  3101. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  3102. return $chn if ($dp eq $dpt &&
  3103. $hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dp}{oper} & $oper);
  3104. }
  3105. }
  3106. }
  3107. else {
  3108. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype})) {
  3109. foreach my $ch (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}}) {
  3110. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}}) {
  3111. return $ch if ($dp eq $dpt &&
  3112. $hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper);
  3113. }
  3114. }
  3115. }
  3116. }
  3117. return -1;
  3118. }
  3119. ######################################################################
  3120. # Get channel number and datapoint name for special datapoint.
  3121. # Valid modes are ontime, ramptime, submit, level
  3122. ######################################################################
  3123. sub HMCCU_GetSwitchDatapoint ($$$)
  3124. {
  3125. my ($hash, $devtype, $mode) = @_;
  3126. my $hmccu_hash = HMCCU_GetHash ($hash);
  3127. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode})) {
  3128. return $hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode};
  3129. }
  3130. else {
  3131. return '';
  3132. }
  3133. }
  3134. ######################################################################
  3135. # Check if datapoint is valid.
  3136. # Parameter chn can be a channel address or a channel number.
  3137. # Parameter dpt can contain a channel number.
  3138. # Parameter oper specifies access flag:
  3139. # 1 = datapoint readable
  3140. # 2 = datapoint writeable
  3141. # Return 1 if ccuflags is set to dptnocheck or datapoint is valid.
  3142. # Otherwise 0.
  3143. ######################################################################
  3144. sub HMCCU_IsValidDatapoint ($$$$$)
  3145. {
  3146. my ($hash, $devtype, $chn, $dpt, $oper) = @_;
  3147. my $hmccu_hash = HMCCU_GetHash ($hash);
  3148. return 0 if (!defined ($hmccu_hash));
  3149. if ($hash->{TYPE} eq 'HMCCU' && !defined ($devtype)) {
  3150. $devtype = HMCCU_GetDeviceType ($hmccu_hash, $chn, 'null');
  3151. }
  3152. my $ccuflags = AttrVal ($hmccu_hash->{NAME}, 'ccuflags', 'null');
  3153. return 1 if ($ccuflags =~ /dptnocheck/);
  3154. return 1 if (!exists ($hmccu_hash->{hmccu}{dp}));
  3155. my $chnno = $chn;
  3156. if (HMCCU_IsChnAddr ($chn, 0)) {
  3157. my ($a, $c) = split(":",$chn);
  3158. $chnno = $c;
  3159. }
  3160. # If datapoint name has format channel-number.datapoint ignore parameter chn
  3161. if ($dpt =~ /^([0-9]{1,2})\.(.+)$/) {
  3162. $chnno = $1;
  3163. $dpt = $2;
  3164. }
  3165. return (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}) &&
  3166. ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}{oper} & $oper)) ? 1 : 0;
  3167. }
  3168. ######################################################################
  3169. # Get list of device or channel addresses for which device or channel
  3170. # name matches regular expression.
  3171. # Parameter mode can be 'dev' or 'chn'.
  3172. # Return number of matching entries.
  3173. ######################################################################
  3174. sub HMCCU_GetMatchingDevices ($$$$)
  3175. {
  3176. my ($hash, $regexp, $mode, $listref) = @_;
  3177. my $c = 0;
  3178. foreach my $name (sort keys %{$hash->{hmccu}{adr}}) {
  3179. next if ($name !~/$regexp/ || $hash->{hmccu}{adr}{$name}{addtype} ne $mode ||
  3180. $hash->{hmccu}{adr}{$name}{valid} == 0);
  3181. push (@$listref, $hash->{hmccu}{adr}{$name}{address});
  3182. $c++;
  3183. }
  3184. return $c;
  3185. }
  3186. ######################################################################
  3187. # Get name of a CCU device by address.
  3188. # Channel number will be removed if specified.
  3189. ######################################################################
  3190. sub HMCCU_GetDeviceName ($$$)
  3191. {
  3192. my ($hash, $addr, $default) = @_;
  3193. if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) {
  3194. $addr =~ s/:[0-9]+$//;
  3195. if (exists ($hash->{hmccu}{dev}{$addr})) {
  3196. return $hash->{hmccu}{dev}{$addr}{name};
  3197. }
  3198. }
  3199. return $default;
  3200. }
  3201. ######################################################################
  3202. # Get name of a CCU device channel by address.
  3203. ######################################################################
  3204. sub HMCCU_GetChannelName ($$$)
  3205. {
  3206. my ($hash, $addr, $default) = @_;
  3207. if (HMCCU_IsChnAddr ($addr, 0)) {
  3208. if (exists ($hash->{hmccu}{dev}{$addr})) {
  3209. return $hash->{hmccu}{dev}{$addr}{name};
  3210. }
  3211. }
  3212. return $default;
  3213. }
  3214. ######################################################################
  3215. # Get type of a CCU device by address.
  3216. # Channel number will be removed if specified.
  3217. ######################################################################
  3218. sub HMCCU_GetDeviceType ($$$)
  3219. {
  3220. my ($hash, $addr, $default) = @_;
  3221. if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) {
  3222. $addr =~ s/:[0-9]+$//;
  3223. if (exists ($hash->{hmccu}{dev}{$addr})) {
  3224. return $hash->{hmccu}{dev}{$addr}{type};
  3225. }
  3226. }
  3227. return $default;
  3228. }
  3229. ######################################################################
  3230. # Get number of channels of a CCU device.
  3231. # Channel number will be removed if specified.
  3232. ######################################################################
  3233. sub HMCCU_GetDeviceChannels ($$$)
  3234. {
  3235. my ($hash, $addr, $default) = @_;
  3236. if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) {
  3237. $addr =~ s/:[0-9]+$//;
  3238. if (exists ($hash->{hmccu}{dev}{$addr})) {
  3239. return $hash->{hmccu}{dev}{$addr}{channels};
  3240. }
  3241. }
  3242. return 0;
  3243. }
  3244. ######################################################################
  3245. # Get interface of a CCU device by address.
  3246. # Channel number will be removed if specified.
  3247. ######################################################################
  3248. sub HMCCU_GetDeviceInterface ($$$)
  3249. {
  3250. my ($hash, $addr, $default) = @_;
  3251. if (HMCCU_IsDevAddr ($addr, 0) || HMCCU_IsChnAddr ($addr, 0)) {
  3252. $addr =~ s/:[0-9]+$//;
  3253. if (exists ($hash->{hmccu}{dev}{$addr})) {
  3254. return $hash->{hmccu}{dev}{$addr}{interface};
  3255. }
  3256. }
  3257. return $default;
  3258. }
  3259. ######################################################################
  3260. # Get address of a CCU device or channel by CCU name or FHEM device
  3261. # name defined via HMCCUCHN or HMCCUDEV. FHEM device names must be
  3262. # preceded by "hmccu:". CCU names can be preceded by "ccu:".
  3263. # Return array with device address and channel no. If name is not
  3264. # found or refers to a device the specified default values will be
  3265. # returned.
  3266. ######################################################################
  3267. sub HMCCU_GetAddress ($$$$)
  3268. {
  3269. my ($hash, $name, $defadd, $defchn) = @_;
  3270. my $add = $defadd;
  3271. my $chn = $defchn;
  3272. my $chnno = $defchn;
  3273. if ($name =~ /^hmccu:.+$/) {
  3274. $name =~ s/^hmccu://;
  3275. if ($name =~ /^([^:]+):([0-9]{1,2})$/) {
  3276. $name = $1;
  3277. $chnno = $2;
  3278. }
  3279. return ($defadd, $defchn) if (!exists ($defs{$name}));
  3280. my $dh = $defs{$name};
  3281. return ($defadd, $defchn) if ($dh->{TYPE} ne 'HMCCUCHN' && $dh->{TYPE} ne 'HMCCUDEV');
  3282. ($add, $chn) = HMCCU_SplitChnAddr ($dh->{ccuaddr});
  3283. $chn = $chnno if ($chn eq '');
  3284. return ($add, $chn);
  3285. }
  3286. elsif ($name =~ /^ccu:.+$/) {
  3287. $name =~ s/^ccu://;
  3288. }
  3289. if (exists ($hash->{hmccu}{adr}{$name})) {
  3290. # Address known by HMCCU
  3291. my $addr = $hash->{hmccu}{adr}{$name}{address};
  3292. if (HMCCU_IsChnAddr ($addr, 0)) {
  3293. ($add, $chn) = split (":", $addr);
  3294. }
  3295. elsif (HMCCU_IsDevAddr ($addr, 0)) {
  3296. $add = $addr;
  3297. }
  3298. }
  3299. else {
  3300. # Address not known. Query CCU
  3301. my $response = HMCCU_GetCCUObjectAttribute ($hash, $name, "Address()");
  3302. if (defined ($response)) {
  3303. if (HMCCU_IsChnAddr ($response, 0)) {
  3304. ($add, $chn) = split (":", $response);
  3305. $hash->{hmccu}{adr}{$name}{address} = $response;
  3306. $hash->{hmccu}{adr}{$name}{addtype} = 'chn';
  3307. }
  3308. elsif (HMCCU_IsDevAddr ($response, 0)) {
  3309. $add = $response;
  3310. $hash->{hmccu}{adr}{$name}{address} = $response;
  3311. $hash->{hmccu}{adr}{$name}{addtype} = 'dev';
  3312. }
  3313. }
  3314. }
  3315. return ($add, $chn);
  3316. }
  3317. ######################################################################
  3318. # Check if parameter is a channel address (syntax)
  3319. # f=1: Interface required.
  3320. ######################################################################
  3321. sub HMCCU_IsChnAddr ($$)
  3322. {
  3323. my ($id, $f) = @_;
  3324. if ($f) {
  3325. return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ ||
  3326. $id =~ /^.+\.[0-9A-F]{12,14}:[0-9]{1,2}$/ ||
  3327. $id =~ /^.+\.OL-.+:[0-9]{1,2}$/ ||
  3328. $id =~ /^.+\.BidCoS-RF:[0-9]{1,2}$/) ? 1 : 0;
  3329. }
  3330. else {
  3331. return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ ||
  3332. $id =~ /^[0-9A-F]{12,14}:[0-9]{1,2}$/ ||
  3333. $id =~ /^OL-.+:[0-9]{1,2}$/ ||
  3334. $id =~ /^BidCoS-RF:[0-9]{1,2}$/) ? 1 : 0;
  3335. }
  3336. }
  3337. ######################################################################
  3338. # Check if parameter is a device address (syntax)
  3339. # f=1: Interface required.
  3340. ######################################################################
  3341. sub HMCCU_IsDevAddr ($$)
  3342. {
  3343. my ($id, $f) = @_;
  3344. if ($f) {
  3345. return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}$/ ||
  3346. $id =~ /^.+\.[0-9A-F]{12,14}$/ ||
  3347. $id =~ /^.+\.OL-.+$/ ||
  3348. $id =~ /^.+\.BidCoS-RF$/) ? 1 : 0;
  3349. }
  3350. else {
  3351. return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}$/ ||
  3352. $id =~ /^[0-9A-F]{12,14}$/ ||
  3353. $id =~ /^OL-.+$/ ||
  3354. $id eq 'BidCoS-RF') ? 1 : 0;
  3355. }
  3356. }
  3357. ######################################################################
  3358. # Split channel address into device address and channel number.
  3359. # Returns device address only if parameter is already a device address.
  3360. ######################################################################
  3361. sub HMCCU_SplitChnAddr ($)
  3362. {
  3363. my ($addr) = @_;
  3364. if (HMCCU_IsChnAddr ($addr, 0)) {
  3365. return split (":", $addr);
  3366. }
  3367. elsif (HMCCU_IsDevAddr ($addr, 0)) {
  3368. return ($addr, '');
  3369. }
  3370. return ('', '');
  3371. }
  3372. ######################################################################
  3373. # Query object attribute from CCU. Attribute must be a valid method
  3374. # for specified object, i.e. Address()
  3375. ######################################################################
  3376. sub HMCCU_GetCCUObjectAttribute ($$$)
  3377. {
  3378. my ($hash, $object, $attr) = @_;
  3379. my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$object.'").'.$attr;
  3380. my $response = GetFileFromURL ($url);
  3381. if (defined ($response) && $response !~ /<r1>null</) {
  3382. if ($response =~ /<r1>(.+)<\/r1>/) {
  3383. return $1;
  3384. }
  3385. }
  3386. return undef;
  3387. }
  3388. ######################################################################
  3389. # Get list of client devices matching the specified criteria.
  3390. # If no criteria is specified all device names will be returned.
  3391. # Parameters modexp and namexp are regular expressions for module
  3392. # name and device name. Parameter internal contains an expression
  3393. # like internal=valueexp.
  3394. # All parameters can be undefined. In this case all devices will be
  3395. # returned.
  3396. ######################################################################
  3397. sub HMCCU_FindClientDevices ($$$$)
  3398. {
  3399. my ($hash, $modexp, $namexp, $internal) = @_;
  3400. my @devlist = ();
  3401. foreach my $d (keys %defs) {
  3402. my $ch = $defs{$d};
  3403. next if (!defined ($ch->{TYPE}) || !defined ($ch->{NAME}));
  3404. next if (defined ($modexp) && $ch->{TYPE} !~ /$modexp/);
  3405. next if (defined ($namexp) && $ch->{NAME} !~ /$namexp/);
  3406. next if (defined ($hash) && exists ($ch->{IODev}) && $ch->{IODev} != $hash);
  3407. if (defined ($internal)) {
  3408. my ($i, $v) = split ('=', $internal);
  3409. next if (defined ($v) && exists ($ch->{$i}) && $ch->{$i} !~ /$v/);
  3410. }
  3411. push @devlist, $ch->{NAME};
  3412. }
  3413. return @devlist;
  3414. }
  3415. ######################################################################
  3416. # Get name of assigned client device of type HMCCURPC.
  3417. # Create a HMCCURPC device if none is found and parameter create
  3418. # is set to 1.
  3419. # Return empty string if HMCCURPC device cannot be identified.
  3420. ######################################################################
  3421. sub HMCCU_GetRPCDevice ($$)
  3422. {
  3423. my ($hash, $create) = @_;
  3424. my $name = $hash->{NAME};
  3425. my $rpcdevname = '';
  3426. # RPC device already defined
  3427. if (defined ($hash->{RPCDEV})) {
  3428. if (exists ($defs{$hash->{RPCDEV}})) {
  3429. if ($defs{$hash->{RPCDEV}}->{IODev} == $hash) {
  3430. $rpcdevname = $hash->{RPCDEV};
  3431. }
  3432. else {
  3433. Log3 $name, 1, "HMCCU: RPC device $rpcdevname is not assigned to $name";
  3434. }
  3435. }
  3436. else {
  3437. Log3 $name, 1, "HMCCU: RPC device $rpcdevname not found";
  3438. }
  3439. }
  3440. else {
  3441. # Search for HMCCURPC devices associated with I/O device
  3442. my @devlist = HMCCU_FindClientDevices ($hash, 'HMCCURPC', undef, undef);
  3443. my $devcnt = scalar (@devlist);
  3444. if ($devcnt == 0 && $create) {
  3445. # Define HMCCURPC device with same room and group as HMCCU device
  3446. $rpcdevname = $name."_rpc";
  3447. Log3 $name, 1, "HMCCU: Creating new RPC device $rpcdevname";
  3448. my $ret = CommandDefine (undef, $rpcdevname." HMCCURPC ".$hash->{host});
  3449. if (!defined ($ret)) {
  3450. # HMCCURPC device created. Copy some attributes from HMCCU device
  3451. my $room = AttrVal ($name, 'room', '');
  3452. CommandAttr (undef, "$rpcdevname room $room") if ($room ne '');
  3453. my $group = AttrVal ($name, 'group', '');
  3454. CommandAttr (undef, "$rpcdevname group $group") if ($group ne '');
  3455. my $icon = AttrVal ($name, 'icon', '');
  3456. CommandAttr (undef, "$rpcdevname icon $icon") if ($icon ne '');
  3457. $hash->{RPCDEV} = $rpcdevname;
  3458. CommandSave (undef, undef);
  3459. }
  3460. else {
  3461. Log3 $name, 1, "HMCCU: Definition of RPC device failed. $ret";
  3462. $rpcdevname = '';
  3463. }
  3464. }
  3465. elsif ($devcnt == 1) {
  3466. $rpcdevname = $devlist[0];
  3467. $hash->{RPCDEV} = $devlist[0];
  3468. }
  3469. elsif ($devcnt > 1) {
  3470. # Found more than 1 HMCCURPC device
  3471. Log3 $name, 2, "HMCCU: Found more than one HMCCURPC device. Specify device with attribute rpcdevice";
  3472. }
  3473. }
  3474. return $rpcdevname;
  3475. }
  3476. ######################################################################
  3477. # Get hash of HMCCU IO device which is responsible for device or
  3478. # channel specified by parameter
  3479. ######################################################################
  3480. sub HMCCU_FindIODevice ($)
  3481. {
  3482. my ($param) = @_;
  3483. foreach my $dn (sort keys %defs) {
  3484. my $ch = $defs{$dn};
  3485. next if (!exists ($ch->{TYPE}));
  3486. next if ($ch->{TYPE} ne 'HMCCU');
  3487. return $ch if (HMCCU_IsValidDeviceOrChannel ($ch, $param));
  3488. }
  3489. return undef;
  3490. }
  3491. ######################################################################
  3492. # Get hash of HMCCU IO device. Useful for client devices. Accepts hash
  3493. # of HMCCU, HMCCUDEV or HMCCUCHN device as parameter.
  3494. ######################################################################
  3495. sub HMCCU_GetHash ($@)
  3496. {
  3497. my ($hash) = @_;
  3498. if (defined ($hash) && $hash != 0) {
  3499. if ($hash->{TYPE} eq 'HMCCUDEV' || $hash->{TYPE} eq 'HMCCUCHN') {
  3500. return $hash->{IODev} if (exists ($hash->{IODev}));
  3501. return HMCCU_FindIODevice ($hash->{ccuaddr}) if (exists ($hash->{ccuaddr}));
  3502. }
  3503. elsif ($hash->{TYPE} eq 'HMCCU') {
  3504. return $hash;
  3505. }
  3506. }
  3507. # Search for first HMCCU device
  3508. foreach my $dn (sort keys %defs) {
  3509. my $ch = $defs{$dn};
  3510. next if (!exists ($ch->{TYPE}));
  3511. return $ch if ($ch->{TYPE} eq 'HMCCU');
  3512. }
  3513. return undef;
  3514. }
  3515. ######################################################################
  3516. # Get attribute of client device. Fallback to attribute of IO device.
  3517. ######################################################################
  3518. sub HMCCU_GetAttribute ($$$$)
  3519. {
  3520. my ($hmccu_hash, $cl_hash, $attr_name, $attr_def) = @_;
  3521. my $value = AttrVal ($cl_hash->{NAME}, $attr_name, '');
  3522. $value = AttrVal ($hmccu_hash->{NAME}, $attr_name, $attr_def) if ($value eq '');
  3523. return $value;
  3524. }
  3525. ######################################################################
  3526. # Get number of occurrences of datapoint.
  3527. # Return 0 if datapoint does not exist.
  3528. ######################################################################
  3529. sub HMCCU_GetDatapointCount ($$$)
  3530. {
  3531. my ($hash, $devtype, $dpt) = @_;
  3532. if (exists ($hash->{hmccu}{dp}{$devtype}{cnt}{$dpt})) {
  3533. return $hash->{hmccu}{dp}{$devtype}{cnt}{$dpt};
  3534. }
  3535. else {
  3536. return 0;
  3537. }
  3538. }
  3539. ######################################################################
  3540. # Get channels and datapoints from attributes statechannel,
  3541. # statedatapoint and controldatapoint.
  3542. # Return attribute values. Attribute controldatapoint is splitted into
  3543. # controlchannel and datapoint name. If attribute statedatapoint
  3544. # contains channel number it is splitted into statechannel and
  3545. # datapoint name.
  3546. ######################################################################
  3547. sub HMCCU_GetSpecialDatapoints ($$$$$)
  3548. {
  3549. # my ($hash, $defsc, $defsd, $defcc, $defcd) = @_;
  3550. my ($hash, $sc, $sd, $cc, $cd) = @_;
  3551. my $name = $hash->{NAME};
  3552. my $type = $hash->{TYPE};
  3553. my $statedatapoint = AttrVal ($name, 'statedatapoint', '');
  3554. my $statechannel = AttrVal ($name, 'statechannel', '');
  3555. my $controldatapoint = AttrVal ($name, 'controldatapoint', '');
  3556. if ($statedatapoint ne '') {
  3557. if ($statedatapoint =~ /^([0-9]+)\.(.+)$/) {
  3558. ($sc, $sd) = ($1, $2);
  3559. }
  3560. else {
  3561. $sd = $statedatapoint;
  3562. }
  3563. }
  3564. $sc = $statechannel if ($statechannel ne '' && $sc eq '');
  3565. if ($controldatapoint ne '') {
  3566. if ($controldatapoint =~ /^([0-9]+)\.(.+)$/) {
  3567. ($cc, $cd) = ($1, $2);
  3568. }
  3569. else {
  3570. $cd = $controldatapoint;
  3571. }
  3572. }
  3573. # For devices of type HMCCUCHN extract channel numbers from CCU device address
  3574. if ($type eq 'HMCCUCHN') {
  3575. $sc = $hash->{ccuaddr};
  3576. $sc =~ s/^[\*]*[0-9A-Z]+://;
  3577. $cc = $sc;
  3578. }
  3579. # Try to find state channel and state datapoint
  3580. my $c = -1;
  3581. if ($sc eq '' && $sd ne '') {
  3582. $c = HMCCU_FindDatapoint ($hash, $hash->{ccutype}, -1, $sd, 3);
  3583. $sc = $c if ($c >= 0);
  3584. }
  3585. # Try to find control channel
  3586. if ($cc eq '' && $cd ne '') {
  3587. $c = HMCCU_FindDatapoint ($hash, $hash->{ccutype}, -1, $cd, 3);
  3588. $cc = $c if ($c >= 0);
  3589. }
  3590. return ($sc, $sd, $cc, $cd);
  3591. }
  3592. ######################################################################
  3593. # Get reading format considering default attribute
  3594. # ccudef-readingformat defined in I/O device.
  3595. # Default reading format for virtual groups is always 'name'.
  3596. ######################################################################
  3597. sub HMCCU_GetAttrReadingFormat ($$)
  3598. {
  3599. my ($clhash, $iohash) = @_;
  3600. my $clname = $clhash->{NAME};
  3601. my $ioname = $iohash->{NAME};
  3602. my $rfdef = '';
  3603. if (exists ($clhash->{ccutype}) && $clhash->{ccutype} =~ /^HM-CC-VG/) {
  3604. $rfdef = 'name';
  3605. }
  3606. else {
  3607. $rfdef = AttrVal ($ioname, 'ccudef-readingformat', 'datapoint');
  3608. }
  3609. return AttrVal ($clname, 'ccureadingformat', $rfdef);
  3610. }
  3611. ######################################################################
  3612. # Get attributes substitute and substexcl considering default
  3613. # attribute ccudef-substitute defined in I/O device.
  3614. # Substitute ${xxx} by datapoint value.
  3615. ######################################################################
  3616. sub HMCCU_GetAttrSubstitute ($$)
  3617. {
  3618. my ($clhash, $iohash) = @_;
  3619. my $fnc = "GetAttrSubstitute";
  3620. my $clname = $clhash->{NAME};
  3621. my $ioname = $iohash->{NAME};
  3622. # my $ccuflags = AttrVal ($clname, 'ccuflags', 'null');
  3623. my $substdef = AttrVal ($ioname, 'ccudef-substitute', '');
  3624. my $subst = AttrVal ($clname, 'substitute', $substdef);
  3625. $subst .= ";$substdef" if ($subst ne $substdef && $substdef ne '');
  3626. HMCCU_Trace ($clhash, 2, $fnc, "subst = $subst");
  3627. return $subst if ($subst !~ /\$\{.+\}/);
  3628. $subst = HMCCU_SubstVariables ($clhash, $subst, undef);
  3629. HMCCU_Trace ($clhash, 2, $fnc, "subst_vars = $subst");
  3630. return $subst;
  3631. }
  3632. ######################################################################
  3633. # Clear RPC queue
  3634. ######################################################################
  3635. sub HMCCU_ResetRPCQueue ($$)
  3636. {
  3637. my ($hash, $port) = @_;
  3638. my $name = $hash->{NAME};
  3639. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  3640. my $clkey = 'CB'.$port;
  3641. if (HMCCU_QueueOpen ($hash, $rpcqueue."_".$port."_".$hash->{CCUNum})) {
  3642. HMCCU_QueueReset ($hash);
  3643. while (defined (HMCCU_QueueDeq ($hash))) { }
  3644. HMCCU_QueueClose ($hash);
  3645. }
  3646. $hash->{hmccu}{rpc}{$clkey}{queue} = '' if (exists ($hash->{hmccu}{rpc}{$clkey}{queue}));
  3647. }
  3648. ######################################################################
  3649. # Process RPC server event
  3650. ######################################################################
  3651. sub HMCCU_ProcessEvent ($$)
  3652. {
  3653. my ($hash, $event) = @_;
  3654. my $name = $hash->{NAME};
  3655. my $rh = \%{$hash->{hmccu}{rpc}};
  3656. return undef if (!defined ($event) || $event eq '');
  3657. my @t = split (/\|/, $event);
  3658. my $tc = scalar (@t);
  3659. # Update statistic counters
  3660. if (exists ($hash->{hmccu}{ev}{$t[0]})) {
  3661. $hash->{hmccu}{evtime} = time ();
  3662. $hash->{hmccu}{ev}{total}++;
  3663. $hash->{hmccu}{ev}{$t[0]}++;
  3664. $hash->{hmccu}{evtimeout} = 0 if ($hash->{hmccu}{evtimeout} == 1);
  3665. }
  3666. else {
  3667. my $errtok = $t[0];
  3668. $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  3669. return HMCCU_Log ($hash, 2, "Received unknown event from CCU: ".$errtok, undef);
  3670. }
  3671. # Check event syntax
  3672. return HMCCU_Log ($hash, 2, "Wrong number of parameters in event $event", undef)
  3673. if (exists ($rpceventargs{$t[0]}) && ($tc-1) != $rpceventargs{$t[0]});
  3674. if ($t[0] eq 'EV') {
  3675. #
  3676. # Update of datapoint
  3677. # Input: EV|Adress|Datapoint|Value
  3678. # Output: EV, DevAdd, ChnNo, Reading='', Value
  3679. #
  3680. return undef if ($tc != 4 || !HMCCU_IsChnAddr ($t[1], 0));
  3681. my ($add, $chn) = split (/:/, $t[1]);
  3682. return ($t[0], $add, $chn, $t[2], $t[3]);
  3683. }
  3684. elsif ($t[0] eq 'SL') {
  3685. #
  3686. # RPC server enters server loop
  3687. # Input: SL|Pid|Servername
  3688. # Output: SL, Servername, Pid
  3689. #
  3690. my $clkey = $t[2];
  3691. return HMCCU_Log ($hash, 0, "Received SL event for unknown RPC server $clkey", undef)
  3692. if (!exists ($rh->{$clkey}));
  3693. Log3 $name, 0, "HMCCU: Received SL event. RPC server $clkey enters server loop";
  3694. $rh->{$clkey}{loop} = 1 if ($rh->{$clkey}{pid} == $t[1]);
  3695. return ($t[0], $clkey, $t[1]);
  3696. }
  3697. elsif ($t[0] eq 'IN') {
  3698. #
  3699. # RPC server initialized
  3700. # Input: IN|INIT|State|Servername
  3701. # Output: IN, Servername, Running, NotRunning, ClientsUpdated, UpdateErrors
  3702. #
  3703. my $clkey = $t[3];
  3704. my $norun = 0;
  3705. my $run = 0;
  3706. my $c_ok = 0;
  3707. my $c_err = 0;
  3708. return HMCCU_Log ($hash, 0, "Received IN event for unknown RPC server $clkey", undef)
  3709. if (!exists ($rh->{$clkey}));
  3710. Log3 $name, 0, "HMCCU: Received IN event. RPC server $clkey initialized.";
  3711. $rh->{$clkey}{state} = $rh->{$clkey}{pid} != 0 ? "running" : "initialized";
  3712. # Check if all RPC servers were initialized. Set overall status
  3713. foreach my $ser (keys %{$rh}) {
  3714. $norun++ if ($rh->{$ser}{state} ne "running" && $rh->{$ser}{pid} != 0);
  3715. $norun++ if ($rh->{$ser}{state} ne "initialized" && $rh->{$ser}{pid} == 0);
  3716. $run++ if ($rh->{$ser}{state} eq "running");
  3717. }
  3718. if ($norun == 0) {
  3719. $hash->{RPCState} = "running";
  3720. readingsSingleUpdate ($hash, "rpcstate", "running", 1);
  3721. HMCCU_SetState ($hash, "OK");
  3722. ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, '.*', 'Attr', 0);
  3723. Log3 $name, 2, "HMCCU: Updated devices. Success=$c_ok Failed=$c_err";
  3724. Log3 $name, 1, "HMCCU: All RPC servers running";
  3725. DoTrigger ($name, "RPC server running");
  3726. }
  3727. $hash->{hmccu}{rpcinit} = $run;
  3728. return ($t[0], $clkey, $run, $norun, $c_ok, $c_err);
  3729. }
  3730. elsif ($t[0] eq 'EX') {
  3731. #
  3732. # RPC server shutdown
  3733. # Input: EX|SHUTDOWN|Pid|Servername
  3734. # Output: EX, Servername, Pid, Flag, Run
  3735. #
  3736. my $clkey = $t[3];
  3737. my $run = 0;
  3738. return HMCCU_Log ($hash, 0, "Received EX event for unknown RPC server $clkey", undef)
  3739. if (!exists ($rh->{$clkey}));
  3740. Log3 $name, 0, "HMCCU: Received EX event. RPC server $clkey terminated.";
  3741. my $f = $hash->{RPCState} eq "restarting" ? 2 : 1;
  3742. delete $rh->{$clkey};
  3743. # Check if all RPC servers were terminated. Set overall status
  3744. foreach my $ser (keys %{$rh}) {
  3745. $run++ if ($rh->{$ser}{state} ne "stopped");
  3746. }
  3747. if ($run == 0) {
  3748. if ($f == 1) {
  3749. $hash->{RPCState} = "stopped";
  3750. readingsSingleUpdate ($hash, "rpcstate", "stopped", 1);
  3751. }
  3752. $hash->{RPCPID} = '0';
  3753. }
  3754. $hash->{hmccu}{rpccount} = $run;
  3755. $hash->{hmccu}{rpcinit} = $run;
  3756. return ($t[0], $clkey, $t[2], $f, $run);
  3757. }
  3758. elsif ($t[0] eq 'ND') {
  3759. #
  3760. # CCU device added
  3761. # Input: ND|C/D|Address|Type|Version|Firmware|RxMode
  3762. # Output: ND, DevAdd, C/D, Type, Version, Firmware, RxMode
  3763. #
  3764. return ($t[0], $t[2], $t[1], $t[3], $t[4], $t[5], $t[6]);
  3765. }
  3766. elsif ($t[0] eq 'DD' || $t[0] eq 'RA') {
  3767. #
  3768. # CCU device added, deleted or readded
  3769. # Input: {DD,RA}|Address
  3770. # Output: {DD,RA}, DevAdd
  3771. #
  3772. return ($t[0], $t[1]);
  3773. }
  3774. elsif ($t[0] eq 'UD') {
  3775. #
  3776. # CCU device updated
  3777. # Input: UD|Address|Hint
  3778. # Output: UD, DevAdd, Hint
  3779. #
  3780. return ($t[0], $t[1], $t[2]);
  3781. }
  3782. elsif ($t[0] eq 'RD') {
  3783. #
  3784. # CCU device replaced
  3785. # Input: RD|Address1|Address2
  3786. # Output: RD, Address1, Address2
  3787. #
  3788. return ($t[0], $t[1], $t[2]);
  3789. }
  3790. elsif ($t[0] eq 'ST') {
  3791. #
  3792. # Statistic data. Store snapshots of sent and received events.
  3793. # Input: ST|nTotal|nEV|nND|nDD|nRD|nRA|nUD|nIN|nSL|nEX
  3794. # Output: ST, ...
  3795. #
  3796. my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX');
  3797. for (my $i=0; $i<10; $i++) {
  3798. $hash->{hmccu}{evs}{$stkeys[$i]} = $t[$i+1];
  3799. $hash->{hmccu}{evr}{$stkeys[$i]} = $hash->{hmccu}{ev}{$stkeys[$i]};
  3800. }
  3801. return @t;
  3802. }
  3803. else {
  3804. my $errtok = $t[0];
  3805. $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  3806. Log3 $name, 2, "HMCCU: Received unknown event from CCU: ".$errtok;
  3807. }
  3808. return undef;
  3809. }
  3810. ######################################################################
  3811. # Timer function for reading RPC queue
  3812. ######################################################################
  3813. sub HMCCU_ReadRPCQueue ($)
  3814. {
  3815. my ($hash) = @_;
  3816. my $name = $hash->{NAME};
  3817. my $eventno = 0;
  3818. my $f = 0;
  3819. my @newdevices;
  3820. my @deldevices;
  3821. my @termpids;
  3822. my $newcount = 0;
  3823. my $devcount = 0;
  3824. my %events = ();
  3825. my %devices = ();
  3826. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  3827. my $rpcinterval = AttrVal ($name, 'rpcinterval', 5);
  3828. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  3829. my $rpctimeout = AttrVal ($name, 'rpcevtimeout', 300);
  3830. my $maxevents = $rpcinterval*10;
  3831. $maxevents = 50 if ($maxevents > 50);
  3832. $maxevents = 10 if ($maxevents < 10);
  3833. my @portlist = HMCCU_GetRPCPortList ($hash);
  3834. foreach my $port (@portlist) {
  3835. my $clkey = 'CB'.$port;
  3836. next if (!exists ($hash->{hmccu}{rpc}{$clkey}{queue}));
  3837. my $queuename = $hash->{hmccu}{rpc}{$clkey}{queue};
  3838. next if ($queuename eq '');
  3839. if (!HMCCU_QueueOpen ($hash, $queuename)) {
  3840. Log3 $name, 1, "HMCCU: Can't open file queue $queuename";
  3841. next;
  3842. }
  3843. my $element = HMCCU_QueueDeq ($hash);
  3844. while (defined ($element)) {
  3845. my ($et, @par) = HMCCU_ProcessEvent ($hash, $element);
  3846. if (defined ($et)) {
  3847. if ($et eq 'EV') {
  3848. $events{$par[0]}{$par[1]}{$par[2]} = $par[3];
  3849. $eventno++;
  3850. last if ($eventno == $maxevents);
  3851. }
  3852. elsif ($et eq 'ND') {
  3853. # push (@newdevices, $par[1]);
  3854. $newcount++ if (!exists ($hash->{hmccu}{dev}{$par[0]}));
  3855. # $hash->{hmccu}{dev}{$par[1]}{chntype} = $par[3];
  3856. $devices{$par[0]}{flag} = 'N';
  3857. $devices{$par[0]}{version} = $par[3];
  3858. if ($par[1] eq 'D') {
  3859. $devices{$par[0]}{type} = $par[2];
  3860. $devices{$par[0]}{firmware} = $par[4];
  3861. $devices{$par[0]}{rxmode} = $par[5];
  3862. }
  3863. else {
  3864. $devices{$par[0]}{usetype} = $par[2];
  3865. }
  3866. $devcount++;
  3867. }
  3868. elsif ($et eq 'DD') {
  3869. # push (@deldevices, $par[0]);
  3870. $devices{$par[0]}{flag} = 'D';
  3871. $devcount++;
  3872. # $delcount++;
  3873. }
  3874. elsif ($et eq 'RD') {
  3875. $devices{$par[0]}{flag} = 'R';
  3876. $devices{$par[0]}{newaddr} = $par[1];
  3877. $devcount++;
  3878. }
  3879. elsif ($et eq 'SL') {
  3880. InternalTimer (gettimeofday()+$HMCCU_INIT_INTERVAL1,
  3881. 'HMCCU_RPCRegisterCallback', $hash, 0);
  3882. $f = -1;
  3883. last;
  3884. }
  3885. elsif ($et eq 'EX') {
  3886. push (@termpids, $par[1]);
  3887. $f = $par[2];
  3888. last;
  3889. }
  3890. }
  3891. last if ($f == -1);
  3892. # Read next element from queue
  3893. $element = HMCCU_QueueDeq ($hash);
  3894. }
  3895. HMCCU_QueueClose ($hash);
  3896. }
  3897. # Update readings
  3898. HMCCU_UpdateMultipleDevices ($hash, \%events) if ($eventno > 0);
  3899. # Update device table and client device parameter
  3900. HMCCU_UpdateDeviceTable ($hash, \%devices) if ($devcount > 0);
  3901. return if ($f == -1);
  3902. # Check if events from CCU timed out
  3903. if ($hash->{hmccu}{evtime} > 0 && time()-$hash->{hmccu}{evtime} > $rpctimeout &&
  3904. $hash->{hmccu}{evtimeout} == 0) {
  3905. $hash->{hmccu}{evtimeout} = 1;
  3906. $hash->{ccustate} = HMCCU_TCPConnect ($hash->{host}, 8181) ? 'timeout' : 'unreachable';
  3907. Log3 $name, 2, "HMCCU: Received no events from CCU since $rpctimeout seconds";
  3908. DoTrigger ($name, "No events from CCU since $rpctimeout seconds");
  3909. }
  3910. else {
  3911. $hash->{ccustate} = 'active' if ($hash->{ccustate} ne 'active');
  3912. }
  3913. my @hm_pids;
  3914. my @hm_tids;
  3915. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  3916. my $nhm_pids = scalar (@hm_pids);
  3917. my $nhm_tids = scalar (@hm_tids);
  3918. Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. f=$f" if ($nhm_tids > 0);
  3919. if ($f > 0) {
  3920. # At least one RPC server has been stopped. Update PID list
  3921. $hash->{RPCPID} = $nhm_pids > 0 ? join(',',@hm_pids) : '0';
  3922. Log3 $name, 0, "HMCCU: RPC server(s) with PID(s) ".join(',',@termpids)." shut down. f=$f";
  3923. # Output statistic counters
  3924. foreach my $cnt (sort keys %{$hash->{hmccu}{ev}}) {
  3925. Log3 $name, 2, "HMCCU: Eventcount $cnt = ".$hash->{hmccu}{ev}{$cnt};
  3926. }
  3927. }
  3928. if ($f == 2 && $nhm_pids == 0) {
  3929. # All RPC servers terminated and restart flag set
  3930. if ($ccuflags =~ /intrpc/) {
  3931. return if (HMCCU_StartIntRPCServer ($hash));
  3932. }
  3933. Log3 $name, 0, "HMCCU: Restart of RPC server failed";
  3934. }
  3935. if ($nhm_pids > 0) {
  3936. # Reschedule reading of RPC queues if at least one RPC server is running
  3937. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  3938. }
  3939. else {
  3940. # No more RPC servers active
  3941. Log3 $name, 0, "HMCCU: Periodical check found no RPC Servers";
  3942. # Deregister existing callbacks
  3943. HMCCU_RPCDeRegisterCallback ($hash);
  3944. # Cleanup hash variables
  3945. my @clkeylist = keys %{$hash->{hmccu}{rpc}};
  3946. foreach my $clkey (@clkeylist) {
  3947. delete $hash->{hmccu}{rpc}{$clkey};
  3948. }
  3949. $hash->{hmccu}{rpccount} = 0;
  3950. $hash->{hmccu}{rpcinit} = 0;
  3951. $hash->{RPCPID} = '0';
  3952. $hash->{RPCPRC} = 'none';
  3953. $hash->{RPCState} = "stopped";
  3954. Log3 $name, 0, "HMCCU: All RPC servers stopped";
  3955. readingsSingleUpdate ($hash, "rpcstate", "stopped", 1);
  3956. DoTrigger ($name, "All RPC servers stopped");
  3957. }
  3958. }
  3959. ######################################################################
  3960. # Execute Homematic script on CCU.
  3961. # Parameters: device-hash, script-code or script-name, parameter-hash
  3962. # If content of hmscript starts with a ! the following text is treated
  3963. # as name of an internal HomeMatic script function.
  3964. # If content of hmscript is enclosed in [] the content is treated as
  3965. # HomeMatic script code.
  3966. # Otherwise hmscript is the name of a file containing Homematic script
  3967. # code.
  3968. # Return script output or error message starting with "ERROR:".
  3969. ######################################################################
  3970. sub HMCCU_HMScriptExt ($$$)
  3971. {
  3972. my ($hash, $hmscript, $params) = @_;
  3973. my $name = $hash->{NAME};
  3974. my $host = $hash->{host};
  3975. my $code = $hmscript;
  3976. my $scrname = '';
  3977. # Check for internal script
  3978. if ($hmscript =~ /^!(.*)$/) {
  3979. $scrname = $1;
  3980. return "ERROR: Can't find internal script $scrname" if (!exists ($HMCCU_SCRIPTS->{$scrname}));
  3981. $code = $HMCCU_SCRIPTS->{$scrname}{code};
  3982. }
  3983. elsif ($hmscript =~ /^\[(.*)\]$/) {
  3984. $code = $1;
  3985. }
  3986. else {
  3987. if (open (SCRFILE, "<$hmscript")) {
  3988. my @lines = <SCRFILE>;
  3989. $code = join ("\n", @lines);
  3990. close (SCRFILE);
  3991. }
  3992. else {
  3993. return "ERROR: Can't open script file";
  3994. }
  3995. }
  3996. # Check and replace variables
  3997. if (defined ($params)) {
  3998. my @parnames = keys %{$params};
  3999. if ($scrname ne '') {
  4000. if (scalar (@parnames) != $HMCCU_SCRIPTS->{$scrname}{parameters}) {
  4001. return "ERROR: Wrong number of parameters. Usage: $scrname ".
  4002. $HMCCU_SCRIPTS->{$scrname}{syntax};
  4003. }
  4004. foreach my $p (split (/[, ]+/, $HMCCU_SCRIPTS->{$scrname}{syntax})) {
  4005. return "ERROR: Missing definition of parameter $p" if (!exists ($params->{$p}));
  4006. }
  4007. }
  4008. foreach my $svar (keys %{$params}) {
  4009. next if ($code !~ /\$$svar/);
  4010. $code =~ s/\$$svar/$params->{$svar}/g;
  4011. }
  4012. }
  4013. else {
  4014. if ($scrname ne '' && $HMCCU_SCRIPTS->{$scrname}{parameters} > 0) {
  4015. return "ERROR: Wrong number of parameters. Usage: $scrname ".
  4016. $HMCCU_SCRIPTS->{$scrname}{syntax};
  4017. }
  4018. }
  4019. # Execute script on CCU
  4020. my $url = "http://".$host.":8181/tclrega.exe";
  4021. my $ua = new LWP::UserAgent ();
  4022. my $response = $ua->post($url, Content => $code);
  4023. return "ERROR: HMScript failed. ".$response->status_line() if (! $response->is_success ());
  4024. my $output = $response->content;
  4025. $output =~ s/<xml>.*<\/xml>//;
  4026. $output =~ s/\r//g;
  4027. return $output;
  4028. }
  4029. ######################################################################
  4030. # Bulk update of reading considering attribute substexcl.
  4031. ######################################################################
  4032. sub HMCCU_BulkUpdate ($$$$)
  4033. {
  4034. my ($hash, $reading, $orgval, $subval) = @_;
  4035. my $excl = AttrVal ($hash->{NAME}, 'substexcl', '');
  4036. readingsBulkUpdate ($hash, $reading, ($excl ne '' && $reading =~ /$excl/ ? $orgval : $subval));
  4037. }
  4038. ######################################################################
  4039. # Get datapoint value from CCU and update reading.
  4040. ######################################################################
  4041. sub HMCCU_GetDatapoint ($@)
  4042. {
  4043. my ($hash, $param) = @_;
  4044. my $name = $hash->{NAME};
  4045. my $type = $hash->{TYPE};
  4046. my $fnc = "GetDatapoint";
  4047. my $hmccu_hash;
  4048. my $value = '';
  4049. $hmccu_hash = HMCCU_GetHash ($hash);
  4050. return (-3, $value) if (!defined ($hmccu_hash));
  4051. return (-4, $value) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted');
  4052. my $ccureadings = AttrVal ($name, 'ccureadings', 1);
  4053. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash);
  4054. my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints (
  4055. $hash, '', 'STATE', '', '');
  4056. my $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value');
  4057. # my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  4058. my $url = 'http://'.$hmccu_hash->{host}.':8181/do.exe?r1=dom.GetObject("';
  4059. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  4060. $HMCCU_FLAG_INTERFACE);
  4061. if ($flags == $HMCCU_FLAGS_IACD) {
  4062. $url .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").'.$ccuget.'()';
  4063. }
  4064. elsif ($flags == $HMCCU_FLAGS_NCD) {
  4065. $url .= $nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'()';
  4066. ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', '');
  4067. }
  4068. else {
  4069. return (-1, $value);
  4070. }
  4071. HMCCU_Trace ($hash, 2, $fnc, "URL=$url, param=$param, ccuget=$ccuget");
  4072. my $rawresponse = GetFileFromURL ($url);
  4073. my $response = $rawresponse;
  4074. $response =~ m/<r1>(.*)<\/r1>/;
  4075. $value = $1;
  4076. HMCCU_Trace ($hash, 2, $fnc, "Response = ".$rawresponse);
  4077. if (defined ($value) && $value ne '' && $value ne 'null') {
  4078. $value = HMCCU_UpdateSingleDatapoint ($hash, $chn, $dpt, $value);
  4079. HMCCU_Trace ($hash, 2, $fnc, "Value of $chn.$dpt = $value");
  4080. return (1, $value);
  4081. }
  4082. else {
  4083. Log3 $name, 1, "$type: Error URL = ".$url;
  4084. return (-2, '');
  4085. }
  4086. }
  4087. ######################################################################
  4088. # Set datapoint on CCU.
  4089. # Parameter param is a valid CCU or FHEM datapoint specification:
  4090. # [ccu:]address:channelnumber.datapoint
  4091. # [ccu:]channelname.datapoint
  4092. # hmccu:hmccudev_name.channelnumber.datapoint
  4093. # hmccu:hmccuchn_name.datapoint
  4094. ######################################################################
  4095. sub HMCCU_SetDatapoint ($$$)
  4096. {
  4097. my ($hash, $param, $value) = @_;
  4098. my $fnc = "SetDatapoint";
  4099. my $type = $hash->{TYPE};
  4100. my $hmccu_hash = HMCCU_GetHash ($hash);
  4101. return -3 if (!defined ($hmccu_hash));
  4102. return -4 if (exists ($hash->{ccudevstate}) && $hash->{ccudevstate} eq 'deleted');
  4103. my $name = $hmccu_hash->{NAME};
  4104. my $cdname = $hash->{NAME};
  4105. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash);
  4106. my $ccuverify = AttrVal ($cdname, 'ccuverify', 0);
  4107. HMCCU_Trace ($hash, 2, $fnc, "param=$param, value=$value");
  4108. if ($param =~ /^hmccu:.+$/) {
  4109. my @t = split (/\./, $param);
  4110. return -1 if (scalar (@t) < 2 || scalar (@t) > 3);
  4111. my $fhdpt = pop @t;
  4112. my ($fhadd, $fhchn) = HMCCU_GetAddress ($hmccu_hash, $t[0], '', '');
  4113. $fhchn = $t[1] if (scalar (@t) == 2);
  4114. return -1 if ($fhadd eq '' || $fhchn eq '');
  4115. $param = "$fhadd:$fhchn.$fhdpt";
  4116. }
  4117. elsif ($param =~ /^ccu:(.+)$/) {
  4118. $param = $1;
  4119. }
  4120. my $url = 'http://'.$hmccu_hash->{host}.':8181/do.exe?r1=dom.GetObject("';
  4121. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  4122. $HMCCU_FLAG_INTERFACE);
  4123. return -1 if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD);
  4124. if ($hash->{ccutype} eq 'HM-Dis-EP-WM55' && $dpt eq 'SUBMIT') {
  4125. $value = HMCCU_EncodeEPDisplay ($value);
  4126. }
  4127. else {
  4128. $value = HMCCU_ScaleValue ($hash, $dpt, $value, 1);
  4129. }
  4130. if ($flags == $HMCCU_FLAGS_IACD) {
  4131. $url .= $int.'.'.$add.':'.$chn.'.'.$dpt.'").State('.$value.')';
  4132. $nam = HMCCU_GetChannelName ($hmccu_hash, $add.":".$chn, '');
  4133. }
  4134. elsif ($flags == $HMCCU_FLAGS_NCD) {
  4135. $url .= $nam.'").DPByHssDP("'.$dpt.'").State('.$value.')';
  4136. ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', '');
  4137. }
  4138. my $addr = $add.":".$chn;
  4139. my $response = GetFileFromURL ($url);
  4140. HMCCU_Trace ($hash, 2, $fnc,
  4141. "Addr=$addr Name=$nam<br>".
  4142. "Script response = \n".(defined ($response) ? $response: 'undef')."<br>".
  4143. "Script = \n".$url);
  4144. return -2 if (!defined ($response) || $response =~ /<r1>null</);
  4145. # Verify setting of datapoint value or update reading with new datapoint value
  4146. if (HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $addr, $dpt, 1)) {
  4147. if ($ccuverify == 1) {
  4148. # usleep (100000);
  4149. my ($rc, $result) = HMCCU_GetDatapoint ($hash, $param);
  4150. return $rc;
  4151. }
  4152. elsif ($ccuverify == 2) {
  4153. HMCCU_UpdateSingleDatapoint ($hash, $chn, $dpt, $value);
  4154. }
  4155. }
  4156. return 0;
  4157. }
  4158. ######################################################################
  4159. # Scale, spread and/or shift datapoint value.
  4160. # Mode: 0 = Get/Divide, 1 = Set/Multiply
  4161. # Supports reversing of value if value range is specified. Syntax for
  4162. # Rule is:
  4163. # Datapoint:Factor
  4164. # [!]Datapoint:Min:Max:Range1:Range2
  4165. # If Datapoint name starts with a ! the value is reversed. In case of
  4166. # an error original value is returned.
  4167. ######################################################################
  4168. sub HMCCU_ScaleValue ($$$$)
  4169. {
  4170. my ($hash, $dpt, $value, $mode) = @_;
  4171. my $name = $hash->{NAME};
  4172. my $ccuscaleval = AttrVal ($name, 'ccuscaleval', '');
  4173. return $value if ($ccuscaleval eq '');
  4174. my @sl = split (',', $ccuscaleval);
  4175. foreach my $sr (@sl) {
  4176. my $f = 1.0;
  4177. my @a = split (':', $sr);
  4178. my $n = scalar (@a);
  4179. next if ($n != 2 && $n != 5);
  4180. my $rev = 0;
  4181. my $dn = $a[0];
  4182. if ($dn =~ /^\!(.+)$/) {
  4183. $dn = $1;
  4184. $rev = 1;
  4185. }
  4186. next if ($dpt ne $dn);
  4187. if ($n == 2) {
  4188. $f = ($a[1] == 0.0) ? 1.0 : $a[1];
  4189. return ($mode == 0) ? $value/$f : $value*$f;
  4190. }
  4191. else {
  4192. # Do not scale if value out of range or interval wrong
  4193. return $value if ($a[1] > $a[2] || $a[3] > $a[4]);
  4194. return $value if ($mode == 0 && ($value < $a[1] || $value > $a[2]));
  4195. # return $value if ($mode == 1 && ($value >= $a[1] && $value <= $a[2]));
  4196. return $value if ($mode == 1 && ($value < $a[3] || $value > $a[4]));
  4197. # Reverse value
  4198. if ($rev) {
  4199. my $dr = ($mode == 0) ? $a[1]+$a[2] : $a[3]+$a[4];
  4200. $value = $dr-$value;
  4201. }
  4202. my $d1 = $a[2]-$a[1];
  4203. my $d2 = $a[4]-$a[3];
  4204. return $value if ($d1 == 0.0 || $d2 == 0.0);
  4205. $f = $d1/$d2;
  4206. return ($mode == 0) ? $value/$f+$a[3] : ($value-$a[3])*$f;
  4207. }
  4208. }
  4209. return $value;
  4210. }
  4211. ######################################################################
  4212. # Get CCU system variables and update readings.
  4213. # System variable readings are stored in I/O device. Unsupported
  4214. # characters in variable names are substituted.
  4215. ######################################################################
  4216. sub HMCCU_GetVariables ($$)
  4217. {
  4218. my ($hash, $pattern) = @_;
  4219. my $count = 0;
  4220. my $result = '';
  4221. my $ccureadings = AttrVal ($hash->{NAME}, 'ccureadings', 1);
  4222. my $response = HMCCU_HMScriptExt ($hash, "!GetVariables", undef);
  4223. return (-2, $response) if ($response eq '' || $response =~ /^ERROR:.*/);
  4224. readingsBeginUpdate ($hash) if ($ccureadings);
  4225. foreach my $vardef (split /\n/, $response) {
  4226. my @vardata = split /=/, $vardef;
  4227. next if (@vardata != 3);
  4228. next if ($vardata[0] !~ /$pattern/);
  4229. my $rn = HMCCU_CorrectName ($vardata[0]);
  4230. my $value = HMCCU_FormatReadingValue ($hash, $vardata[2], $vardata[0]);
  4231. readingsBulkUpdate ($hash, $rn, $value) if ($ccureadings);
  4232. $result .= $vardata[0].'='.$vardata[2]."\n";
  4233. $count++;
  4234. }
  4235. readingsEndUpdate ($hash, 1) if ($hash->{TYPE} ne 'HMCCU' && $ccureadings);
  4236. return ($count, $result);
  4237. }
  4238. ######################################################################
  4239. # Set CCU system variable. If parameter vartype is undefined system
  4240. # variable must exist in CCU. Following variable types are supported:
  4241. # bool, list, number, text. Parameter params is a hash reference of
  4242. # script parameters.
  4243. # Return 0 on success, error code on error.
  4244. ######################################################################
  4245. sub HMCCU_SetVariable ($$$$$)
  4246. {
  4247. my ($hash, $varname, $value, $vartype, $params) = @_;
  4248. my $name = $hash->{NAME};
  4249. my %varfnc = (
  4250. "bool" => "!CreateBoolVariable", "list", "!CreateListVariable",
  4251. "number" => "!CreateNumericVariable", "text", "!CreateStringVariable"
  4252. );
  4253. if (!defined ($vartype)) {
  4254. my $url = 'http://'.$hash->{host}.':8181/do.exe?r1=dom.GetObject("'.$varname.
  4255. '").State("'.$value.'")';
  4256. my $response = GetFileFromURL ($url);
  4257. return HMCCU_Log ($hash, 1, "URL=$url", -2)
  4258. if (!defined ($response) || $response =~ /<r1>null</);
  4259. }
  4260. else {
  4261. return -18 if (!exists ($varfnc{$vartype}));
  4262. # Set default values for variable attributes
  4263. $params->{name} = $varname if (!exists ($params->{name}));
  4264. $params->{init} = $value if (!exists ($params->{init}));
  4265. $params->{unit} = "" if (!exists ($params->{unit}));
  4266. $params->{desc} = "" if (!exists ($params->{desc}));
  4267. $params->{min} = "0" if ($vartype eq 'number' && !exists ($params->{min}));
  4268. $params->{max} = "65000" if ($vartype eq 'number' && !exists ($params->{max}));
  4269. $params->{list} = $value if ($vartype eq 'list' && !exists ($params->{list}));
  4270. $params->{valtrue} = "ist wahr" if ($vartype eq 'bool' && !exists ($params->{valtrue}));
  4271. $params->{valfalse} = "ist falsch" if ($vartype eq 'bool' && !exists ($params->{valfalse}));
  4272. my $rc = HMCCU_HMScriptExt ($hash, $varfnc{$vartype}, $params);
  4273. return HMCCU_Log ($hash, 1, $rc, -2) if ($rc =~ /^ERROR:.*/);
  4274. }
  4275. return 0;
  4276. }
  4277. ######################################################################
  4278. # Update all datapoints / readings of device or channel considering
  4279. # attribute ccureadingfilter.
  4280. # Parameter $ccuget can be 'State', 'Value' or 'Attr'.
  4281. ######################################################################
  4282. sub HMCCU_GetUpdate ($$$)
  4283. {
  4284. my ($cl_hash, $addr, $ccuget) = @_;
  4285. my $name = $cl_hash->{NAME};
  4286. my $type = $cl_hash->{TYPE};
  4287. my $fnc = "GetUpdate";
  4288. my $disable = AttrVal ($name, 'disable', 0);
  4289. return 1 if ($disable == 1);
  4290. my $hmccu_hash = HMCCU_GetHash ($cl_hash);
  4291. return -3 if (!defined ($hmccu_hash));
  4292. return -4 if ($type ne 'HMCCU' && $cl_hash->{ccudevstate} eq 'deleted');
  4293. my $nam = '';
  4294. my $list = '';
  4295. my $script = '';
  4296. $ccuget = HMCCU_GetAttribute ($hmccu_hash, $cl_hash, 'ccuget', 'Value') if ($ccuget eq 'Attr');
  4297. if (HMCCU_IsChnAddr ($addr, 0)) {
  4298. $nam = HMCCU_GetChannelName ($hmccu_hash, $addr, '');
  4299. return -1 if ($nam eq '');
  4300. my ($stadd, $stchn) = HMCCU_SplitChnAddr ($addr);
  4301. my $stnam = HMCCU_GetChannelName ($hmccu_hash, "$stadd:0", '');
  4302. $list = $stnam eq '' ? $nam : $stnam . "," . $nam;
  4303. $script = "!GetDatapointsByChannel";
  4304. }
  4305. elsif (HMCCU_IsDevAddr ($addr, 0)) {
  4306. $nam = HMCCU_GetDeviceName ($hmccu_hash, $addr, '');
  4307. return -1 if ($nam eq '');
  4308. $list = $nam;
  4309. $script = "!GetDatapointsByDevice";
  4310. # Consider members of group device
  4311. if ($type eq 'HMCCUDEV' && $cl_hash->{ccuif} eq 'VirtualDevices' &&
  4312. exists ($cl_hash->{ccugroup})) {
  4313. foreach my $gd (split (",", $cl_hash->{ccugroup})) {
  4314. $nam = HMCCU_GetDeviceName ($hmccu_hash, $gd, '');
  4315. $list .= ','.$nam if ($nam ne '');
  4316. }
  4317. }
  4318. }
  4319. else {
  4320. return -1;
  4321. }
  4322. my $response = HMCCU_HMScriptExt ($hmccu_hash, $script,
  4323. { list => $list, ccuget => $ccuget });
  4324. HMCCU_Trace ($cl_hash, 2, $fnc, "Addr=$addr Name=$nam Script=$script<br>".
  4325. "Script response = \n".$response);
  4326. return -2 if ($response eq '' || $response =~ /^ERROR:.*/);
  4327. my @dpdef = split /\n/, $response;
  4328. my $count = pop (@dpdef);
  4329. return -10 if (!defined ($count) || $count == 0);
  4330. my %events = ();
  4331. foreach my $dp (@dpdef) {
  4332. my ($chnname, $dpspec, $value) = split /=/, $dp;
  4333. next if (!defined ($value));
  4334. my ($iface, $chnadd, $dpt) = split /\./, $dpspec;
  4335. next if (!defined ($dpt));
  4336. my ($add, $chn) = HMCCU_SplitChnAddr ($chnadd);
  4337. next if (!defined ($chn));
  4338. $events{$add}{$chn}{$dpt} = $value;
  4339. }
  4340. HMCCU_UpdateSingleDevice ($hmccu_hash, $cl_hash, \%events);
  4341. return 1;
  4342. }
  4343. ######################################################################
  4344. # Get multiple datapoints of channels and update readings of client
  4345. # devices.
  4346. # Returncodes: -1 = Invalid channel/datapoint in list
  4347. # -2 = CCU script execution failed
  4348. # -3 = Cannot detect IO device
  4349. # On success number of updated readings is returned.
  4350. ######################################################################
  4351. sub HMCCU_GetChannel ($$)
  4352. {
  4353. my ($hash, $chnref) = @_;
  4354. my $name = $hash->{NAME};
  4355. my $type = $hash->{TYPE};
  4356. my $count = 0;
  4357. my $chnlist = '';
  4358. my $result = '';
  4359. my %chnpars;
  4360. my %objects = ();
  4361. my $objcount = 0;
  4362. my $hmccuflags = AttrVal ($name, 'ccuflags', 'null');
  4363. my $ccuget = AttrVal ($name, 'ccuget', 'Value');
  4364. # Build channel list. Datapoints and substitution rules are ignored.
  4365. foreach my $chndef (@$chnref) {
  4366. my ($channel, $substitute) = split /\s+/, $chndef;
  4367. next if (!defined ($channel) || $channel =~ /^#/ || $channel eq '');
  4368. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $channel,
  4369. $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_DATAPOINT);
  4370. if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) {
  4371. $nam = HMCCU_GetChannelName ($hash, $add.':'.$chn, '') if ($flags == $HMCCU_FLAGS_IACD);
  4372. $chnlist = $chnlist eq '' ? $nam : $chnlist.','.$nam;
  4373. $chnpars{$nam}{sub} = $substitute;
  4374. $chnpars{$nam}{dpt} = $dpt;
  4375. }
  4376. else {
  4377. return (-1, $result);
  4378. }
  4379. }
  4380. return (0, $result) if ($chnlist eq '');
  4381. my $response = HMCCU_HMScriptExt ($hash, "!GetChannel",
  4382. { list => $chnlist, ccuget => $ccuget });
  4383. return (-2, $result) if ($response eq '' || $response =~ /^ERROR:.*/);
  4384. # Output format is Channelname=Interface.Channeladdress.Datapoint=Value
  4385. foreach my $dpdef (split /\n/, $response) {
  4386. my ($chnname, $dptaddr, $value) = split /=/, $dpdef;
  4387. next if (!defined ($value));
  4388. my ($iface, $chnaddr, $dpt) = split /\./, $dptaddr;
  4389. next if (!defined ($dpt));
  4390. my ($add, $chn) = HMCCU_SplitChnAddr ($chnaddr);
  4391. next if (!defined ($chn));
  4392. if (defined ($chnpars{$chnname}{dpt}) && $chnpars{$chnname}{dpt} ne '') {
  4393. next if ($dpt !~ $chnpars{$chnname}{dpt});
  4394. }
  4395. $value = HMCCU_Substitute ($value, $chnpars{$chnname}{sub}, 0, $chn, $dpt);
  4396. $objects{$add}{$chn}{$dpt} = $value;
  4397. $objcount++;
  4398. }
  4399. $count = HMCCU_UpdateMultipleDevices ($hash, \%objects) if ($objcount > 0);
  4400. return ($count, $result);
  4401. }
  4402. ######################################################################
  4403. # Get RPC paramSet or paramSetDescription
  4404. ######################################################################
  4405. sub HMCCU_RPCGetConfig ($$$$)
  4406. {
  4407. my ($hash, $param, $mode, $filter) = @_;
  4408. my $name = $hash->{NAME};
  4409. my $type = $hash->{TYPE};
  4410. my $fnc = "RPCGetConfig";
  4411. my $method = $mode eq 'listParamset' ? 'getParamset' : $mode;
  4412. my $addr;
  4413. my $result = '';
  4414. my $res = '';
  4415. my $hmccu_hash = HMCCU_GetHash ($hash);
  4416. return (-3, $result) if (!defined ($hmccu_hash));
  4417. return (-4, $result) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted');
  4418. # my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  4419. my $ccureadings = AttrVal ($name, 'ccureadings', 1);
  4420. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash);
  4421. my $substitute = HMCCU_GetAttrSubstitute ($hash, $hmccu_hash);
  4422. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  4423. $HMCCU_FLAG_FULLADDR);
  4424. return (-1, '') if (!($flags & $HMCCU_FLAG_ADDRESS));
  4425. $addr = $add;
  4426. $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL);
  4427. return (-9, '') if (!exists ($HMCCU_RPC_PORT{$int}));
  4428. my $port = $HMCCU_RPC_PORT{$int};
  4429. if ($HMCCU_RPC_PROT{$port} eq 'B') {
  4430. # Search RPC device
  4431. my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0);
  4432. return (-17, '') if ($rpcdev eq '');
  4433. HMCCU_Trace ($hash, 2, $fnc, "Method=$method Addr=$addr Port=$port");
  4434. $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, $method, $BINRPC_STRING, $addr,
  4435. $BINRPC_STRING, "MASTER");
  4436. }
  4437. else {
  4438. my $url = "http://".$hmccu_hash->{host}.":".$port."/";
  4439. $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port}));
  4440. HMCCU_Trace ($hash, 2, $fnc, "Method=$method Addr=$addr Port=$port");
  4441. my $client = RPC::XML::Client->new ($url);
  4442. $res = $client->simple_request ($method, $addr, "MASTER");
  4443. }
  4444. return (-5, "Function not available") if (!defined ($res));
  4445. if (defined ($res)) {
  4446. HMCCU_Trace ($hash, 2, $fnc,
  4447. "Dump of RPC request $method $addr. Result type=".ref($res)."<br>".
  4448. HMCCU_RefToString ($res));
  4449. }
  4450. if (ref ($res) eq 'HASH') {
  4451. my $parcount = scalar (keys %$res);
  4452. if (exists ($res->{faultString})) {
  4453. Log3 $name, 1, "HMCCU: ".$res->{faultString};
  4454. return (-2, $res->{faultString});
  4455. }
  4456. elsif ($parcount == 0) {
  4457. return (-5, "CCU returned no data");
  4458. }
  4459. }
  4460. else {
  4461. return (-2, defined ($RPC::XML::ERROR) ? $RPC::XML::ERROR : '');
  4462. }
  4463. if ($mode eq 'getParamsetDescription') {
  4464. foreach my $key (sort keys %$res) {
  4465. my $oper = '';
  4466. $oper .= 'R' if ($res->{$key}->{OPERATIONS} & 1);
  4467. $oper .= 'W' if ($res->{$key}->{OPERATIONS} & 2);
  4468. $oper .= 'E' if ($res->{$key}->{OPERATIONS} & 4);
  4469. $result .= $key.": ".$res->{$key}->{TYPE}." [".$oper."]\n";
  4470. }
  4471. }
  4472. elsif ($mode eq 'listParamset') {
  4473. foreach my $key (sort keys %$res) {
  4474. next if ($key !~ /$filter/);
  4475. my $value = $res->{$key};
  4476. $result .= "$key=$value\n";
  4477. }
  4478. }
  4479. else {
  4480. readingsBeginUpdate ($hash) if ($ccureadings);
  4481. foreach my $key (sort keys %$res) {
  4482. next if ($key !~ /$filter/);
  4483. my $value = $res->{$key};
  4484. $result .= "$key=$value\n";
  4485. next if (!$ccureadings);
  4486. $value = HMCCU_FormatReadingValue ($hash, $value, $key);
  4487. $value = HMCCU_Substitute ($value, $substitute, 0, $chn, $key);
  4488. my @readings = HMCCU_GetReadingName ($hash, $int, $add, $chn, $key, $nam, $readingformat);
  4489. foreach my $rn (@readings) {
  4490. next if ($rn eq '');
  4491. $rn = "R-".$rn;
  4492. readingsBulkUpdate ($hash, $rn, $value);
  4493. }
  4494. }
  4495. readingsEndUpdate ($hash, 1) if ($ccureadings);
  4496. }
  4497. return (0, $result);
  4498. }
  4499. ######################################################################
  4500. # Set RPC paramSet
  4501. ######################################################################
  4502. sub HMCCU_RPCSetConfig ($$$)
  4503. {
  4504. my ($hash, $param, $parref) = @_;
  4505. my $name = $hash->{NAME};
  4506. my $type = $hash->{TYPE};
  4507. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  4508. my $addr;
  4509. my $res;
  4510. my $hmccu_hash = HMCCU_GetHash ($hash);
  4511. return -3 if (!defined ($hmccu_hash));
  4512. return -4 if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted');
  4513. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  4514. $HMCCU_FLAG_FULLADDR);
  4515. return -1 if (!($flags & $HMCCU_FLAG_ADDRESS));
  4516. $addr = $add;
  4517. $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL);
  4518. return -9 if (!exists ($HMCCU_RPC_PORT{$int}));
  4519. my $port = $HMCCU_RPC_PORT{$int};
  4520. if ($ccuflags =~ /trace/) {
  4521. my $ps = '';
  4522. foreach my $p (keys %$parref) {
  4523. $ps .= ", ".$p."=".$parref->{$p};
  4524. }
  4525. Log3 $name, 2, "HMCCU: RPCSetConfig: addr=$addr".$ps;
  4526. }
  4527. if ($HMCCU_RPC_PROT{$port} eq 'B') {
  4528. # Search RPC device
  4529. my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0);
  4530. return -17 if ($rpcdev eq '');
  4531. # Rebuild parameter hash for binary encoding
  4532. my %binpar;
  4533. foreach my $e (keys %$parref) {
  4534. $binpar{$e}{T} = $BINRPC_STRING;
  4535. $binpar{$e}{V} = $parref->{$e};
  4536. }
  4537. $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, "putParamset", $BINRPC_STRING, $addr,
  4538. $BINRPC_STRING, "MASTER", $BINRPC_STRUCT, \%binpar);
  4539. }
  4540. else {
  4541. my $url = "http://".$hmccu_hash->{host}.":".$port."/";
  4542. $url .= $HMCCU_RPC_URL{$port} if (exists ($HMCCU_RPC_URL{$port}));
  4543. my $client = RPC::XML::Client->new ($url);
  4544. $res = $client->simple_request ("putParamset", $addr, "MASTER", $parref);
  4545. }
  4546. return -5 if (! defined ($res));
  4547. return HMCCU_Log ($hash, 1, "HMCCU: RPC request failed. ".$res->{faultString}, -2)
  4548. if (ref ($res) && exists ($res->{faultString}));
  4549. return 0;
  4550. }
  4551. ######################################################################
  4552. # *** FILEQUEUE FUNCTIONS ***
  4553. ######################################################################
  4554. ######################################################################
  4555. # Open file queue
  4556. ######################################################################
  4557. sub HMCCU_QueueOpen ($$)
  4558. {
  4559. my ($hash, $queue_file) = @_;
  4560. my $idx_file = $queue_file . '.idx';
  4561. $queue_file .= '.dat';
  4562. my $mode = '0666';
  4563. umask (0);
  4564. $hash->{hmccu}{queue}{block_size} = 64;
  4565. $hash->{hmccu}{queue}{seperator} = "\n";
  4566. $hash->{hmccu}{queue}{sep_length} = length $hash->{hmccu}{queue}{seperator};
  4567. $hash->{hmccu}{queue}{queue_file} = $queue_file;
  4568. $hash->{hmccu}{queue}{idx_file} = $idx_file;
  4569. $hash->{hmccu}{queue}{queue} = new IO::File $queue_file, O_CREAT | O_RDWR, oct($mode) or return 0;
  4570. $hash->{hmccu}{queue}{idx} = new IO::File $idx_file, O_CREAT | O_RDWR, oct($mode) or return 0;
  4571. ### Default ptr to 0, replace it with value in idx file if one exists
  4572. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  4573. $hash->{hmccu}{queue}{idx}->sysread($hash->{hmccu}{queue}{ptr}, 1024);
  4574. $hash->{hmccu}{queue}{ptr} = '0' unless $hash->{hmccu}{queue}{ptr};
  4575. if($hash->{hmccu}{queue}{ptr} > -s $queue_file)
  4576. {
  4577. $hash->{hmccu}{queue}{idx}->truncate(0) or return 0;
  4578. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  4579. $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0;
  4580. }
  4581. return 1;
  4582. }
  4583. ######################################################################
  4584. # Close file queue
  4585. ######################################################################
  4586. sub HMCCU_QueueClose ($)
  4587. {
  4588. my ($hash) = @_;
  4589. if (exists ($hash->{hmccu}{queue})) {
  4590. $hash->{hmccu}{queue}{idx}->close();
  4591. $hash->{hmccu}{queue}{queue}->close();
  4592. delete $hash->{hmccu}{queue};
  4593. }
  4594. }
  4595. sub HMCCU_QueueReset ($)
  4596. {
  4597. my ($hash) = @_;
  4598. $hash->{hmccu}{queue}{idx}->truncate(0) or return 0;
  4599. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  4600. $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0;
  4601. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET);
  4602. return 1;
  4603. }
  4604. ######################################################################
  4605. # Put value in file queue
  4606. ######################################################################
  4607. sub HMCCU_QueueEnq ($$)
  4608. {
  4609. my ($hash, $element) = @_;
  4610. return 0 if (!exists ($hash->{hmccu}{queue}));
  4611. $hash->{hmccu}{queue}{queue}->sysseek(0, SEEK_END);
  4612. $element =~ s/$hash->{hmccu}{queue}{seperator}//g;
  4613. $hash->{hmccu}{queue}{queue}->syswrite($element.$hash->{hmccu}{queue}{seperator}) or return 0;
  4614. return 1;
  4615. }
  4616. ######################################################################
  4617. # Return next value in file queue
  4618. ######################################################################
  4619. sub HMCCU_QueueDeq ($)
  4620. {
  4621. my ($hash) = @_;
  4622. my $name = $hash->{NAME};
  4623. my $sep_length = $hash->{hmccu}{queue}{sep_length};
  4624. my $element = '';
  4625. return undef if (!exists ($hash->{hmccu}{queue}));
  4626. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  4627. my $i;
  4628. while($hash->{hmccu}{queue}{queue}->sysread($_, $hash->{hmccu}{queue}{block_size})) {
  4629. $i = index($_, $hash->{hmccu}{queue}{seperator});
  4630. if($i != -1) {
  4631. $element .= substr($_, 0, $i);
  4632. $hash->{hmccu}{queue}{ptr} += $i + $sep_length;
  4633. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  4634. last;
  4635. }
  4636. else {
  4637. # Seperator not found, go back 'sep_length' spaces to ensure we don't miss it between reads
  4638. Log3 $name, 2, "HMCCU: HMCCU_QueueDeq seperator not found";
  4639. $element .= substr($_, 0, -$sep_length, '');
  4640. $hash->{hmccu}{queue}{ptr} += $hash->{hmccu}{queue}{block_size} - $sep_length;
  4641. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  4642. }
  4643. }
  4644. ## If queue seek pointer is at the EOF, truncate the queue file
  4645. if($hash->{hmccu}{queue}{queue}->sysread($_, 1) == 0)
  4646. {
  4647. $hash->{hmccu}{queue}{queue}->truncate(0) or return undef;
  4648. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET);
  4649. }
  4650. ## Set idx file contents to point to the current seek position in queue file
  4651. $hash->{hmccu}{queue}{idx}->truncate(0) or return undef;
  4652. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  4653. $hash->{hmccu}{queue}{idx}->syswrite($hash->{hmccu}{queue}{ptr}) or return undef;
  4654. return ($element ne '') ? $element : undef;
  4655. }
  4656. ######################################################################
  4657. # *** HELPER FUNCTIONS ***
  4658. ######################################################################
  4659. ######################################################################
  4660. # Determine HomeMatic state considering datapoint values specified
  4661. # in attributes ccudef-hmstatevals and hmstatevals.
  4662. # Return (reading, channel, datapoint, value)
  4663. ######################################################################
  4664. sub HMCCU_GetHMState ($$$)
  4665. {
  4666. my ($name, $ioname, $defval) = @_;
  4667. my @hmstate = ('hmstate', undef, undef, $defval);
  4668. my $fnc = "GetHMState";
  4669. my $clhash = $defs{$name};
  4670. my $cltype = $clhash->{TYPE};
  4671. return @hmstate if ($cltype ne 'HMCCUDEV' && $cltype ne 'HMCCUCHN');
  4672. my $ghmstatevals = AttrVal ($ioname, 'ccudef-hmstatevals', $HMCCU_DEF_HMSTATE);
  4673. my $hmstatevals = AttrVal ($name, 'hmstatevals', $ghmstatevals);
  4674. $hmstatevals .= ";".$ghmstatevals if ($hmstatevals ne $ghmstatevals);
  4675. # Get reading name
  4676. if ($hmstatevals =~ /^=([^;]*);/) {
  4677. $hmstate[0] = $1;
  4678. $hmstatevals =~ s/^=[^;]*;//;
  4679. }
  4680. HMCCU_Trace ($clhash, 2, $fnc, "hmstatevals=$hmstatevals");
  4681. # Default hmstate is equal to state
  4682. $hmstate[3] = ReadingsVal ($name, 'state', undef) if (!defined ($defval));
  4683. # Substitute variables
  4684. $hmstatevals = HMCCU_SubstVariables ($clhash, $hmstatevals, undef);
  4685. my @rulelist = split (";", $hmstatevals);
  4686. foreach my $rule (@rulelist) {
  4687. my ($dptexpr, $subst) = split ('!', $rule, 2);
  4688. my $dp = '';
  4689. next if (!defined ($dptexpr) || !defined ($subst));
  4690. HMCCU_Trace ($clhash, 2, $fnc, "dptexpr=$dptexpr, subst=$subst");
  4691. foreach my $d (keys %{$clhash->{hmccu}{dp}}) {
  4692. HMCCU_Trace ($clhash, 2, $fnc, "Check $d match $dptexpr");
  4693. if ($d =~ /$dptexpr/) {
  4694. $dp = $d;
  4695. last;
  4696. }
  4697. }
  4698. next if ($dp eq '');
  4699. my ($chn, $dpt) = split (/\./, $dp);
  4700. my $value = HMCCU_FormatReadingValue ($clhash, $clhash->{hmccu}{dp}{$dp}{VAL}, $hmstate[0]);
  4701. my ($rc, $newvalue) = HMCCU_SubstRule ($value, $subst, 0);
  4702. return ($hmstate[0], $chn, $dpt, $newvalue) if ($rc);
  4703. }
  4704. return @hmstate;
  4705. }
  4706. ######################################################################
  4707. # Calculate time difference in seconds between current time and
  4708. # specified timestamp
  4709. ######################################################################
  4710. sub HMCCU_GetTimeSpec ($)
  4711. {
  4712. my ($ts) = @_;
  4713. return -1 if ($ts !~ /^[0-9]{2}:[0-9]{2}$/ && $ts !~ /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/);
  4714. my (undef, $h, $m, $s) = GetTimeSpec ($ts);
  4715. return -1 if (!defined ($h));
  4716. $s += $h*3600+$m*60;
  4717. my @lt = localtime;
  4718. my $cs = $lt[2]*3600+$lt[1]*60+$lt[0];
  4719. $s += 86400 if ($cs > $s);
  4720. return ($s-$cs);
  4721. }
  4722. ######################################################################
  4723. # Calculate special readings. Requires hash of client device, channel
  4724. # number and datapoint. Supported functions:
  4725. # dewpoint, absolute humidity, increasing/decreasing counters,
  4726. # minimum/maximum, average, sum.
  4727. # Return readings array with reading/value pairs.
  4728. ######################################################################
  4729. sub HMCCU_CalculateReading ($$$)
  4730. {
  4731. my ($cl_hash, $chnno, $dpt) = @_;
  4732. my $name = $cl_hash->{NAME};
  4733. my @result = ();
  4734. my $ccucalculate = AttrVal ($name, 'ccucalculate', '');
  4735. return @result if ($ccucalculate eq '');
  4736. my @calclist = split (/[;\n]+/, $ccucalculate);
  4737. foreach my $calculation (@calclist) {
  4738. my ($vt, $rn, $dpts) = split (':', $calculation);
  4739. next if (!defined ($rn));
  4740. # Get parameters values stored in device hash
  4741. my @dplist = defined ($dpts) ? split (',', $dpts) : ();
  4742. next if (@dplist > 0 && !(grep { $_ eq "$chnno.$dpt"} @dplist));
  4743. my @pars = ();
  4744. foreach my $dp (@dplist) {
  4745. if (exists ($cl_hash->{hmccu}{dp}{$dp}{VAL})) {
  4746. push @pars, $cl_hash->{hmccu}{dp}{$dp}{VAL};
  4747. }
  4748. }
  4749. if ($vt eq 'dewpoint' || $vt eq 'abshumidity') {
  4750. # Dewpoint and absolute humidity
  4751. next if (scalar (@pars) < 2);
  4752. my ($tmp, $hum) = @pars;
  4753. if ($tmp >= 0.0) {
  4754. $a = 7.5;
  4755. $b = 237.3;
  4756. }
  4757. else {
  4758. $a = 7.6;
  4759. $b = 240.7;
  4760. }
  4761. my $sdd = 6.1078*(10.0**(($a*$tmp)/($b+$tmp)));
  4762. my $dd = $hum/100.0*$sdd;
  4763. if ($vt eq 'dewpoint') {
  4764. my $v = log($dd/6.1078)/log(10.0);
  4765. my $td = $b*$v/($a-$v);
  4766. push (@result, $rn, (sprintf "%.1f", $td));
  4767. }
  4768. else {
  4769. my $af = 100000.0*18.016/8314.3*$dd/($tmp+273.15);
  4770. push (@result, $rn, (sprintf "%.1f", $af));
  4771. }
  4772. }
  4773. elsif ($vt eq 'min' || $vt eq 'max') {
  4774. # Minimum or maximum values
  4775. next if (scalar (@pars) < 1);
  4776. my $newval = shift @pars;
  4777. my $curval = ReadingsVal ($name, $rn, 0);
  4778. $curval = $newval if ($vt eq 'min' && $newval < $curval);
  4779. $curval = $newval if ($vt eq 'max' && $newval > $curval);
  4780. push (@result, $rn, $curval);
  4781. }
  4782. elsif ($vt eq 'inc' || $vt eq 'dec') {
  4783. # Increasing or decreasing values without reset
  4784. next if (scalar (@pars) < 1);
  4785. my $newval = shift @pars;
  4786. my $oldval = ReadingsVal ($name, $rn."_old", 0);
  4787. my $curval = ReadingsVal ($name, $rn, 0);
  4788. if (($vt eq 'inc' && $newval < $curval) || ($vt eq 'dec' && $newval > $curval)) {
  4789. $oldval = $curval;
  4790. push (@result, $rn."_old", $oldval);
  4791. }
  4792. $curval = $newval+$oldval;
  4793. push (@result, $rn, $curval);
  4794. }
  4795. elsif ($vt eq 'avg') {
  4796. # Average value
  4797. next if (scalar (@pars) < 1);
  4798. my $newval = shift @pars;
  4799. my $cnt = ReadingsVal ($name, $rn."_cnt", 0);
  4800. my $sum = ReadingsVal ($name, $rn."_sum", 0);
  4801. $cnt++;
  4802. $sum += $newval;
  4803. my $curval = $sum/$cnt;
  4804. push (@result, $rn."_cnt", $cnt, $rn."_sum", $sum, $rn, $curval);
  4805. }
  4806. elsif ($vt eq 'sum') {
  4807. # Sum of values
  4808. next if (scalar (@pars) < 1);
  4809. my $newval = shift @pars;
  4810. my $curval = ReadingsVal ($name, $rn, 0);
  4811. $curval += $newval;
  4812. push (@result, $rn, $curval);
  4813. }
  4814. }
  4815. return @result;
  4816. }
  4817. ######################################################################
  4818. # Encode command string for e-paper display
  4819. #
  4820. # Parameters:
  4821. #
  4822. # msg := parameter=value[,...]
  4823. #
  4824. # text1-3=Text
  4825. # icon1-3=IconName
  4826. # sound=SoundName
  4827. # signal=SignalName
  4828. # pause=1-160
  4829. # repeat=0-15
  4830. #
  4831. # Returns undef on error or encoded string on success
  4832. ######################################################################
  4833. sub HMCCU_EncodeEPDisplay ($)
  4834. {
  4835. my ($msg) = @_;
  4836. # set defaults
  4837. $msg = '' if (!defined ($msg));
  4838. my %disp_icons = (
  4839. ico_off => '0x80', ico_on => '0x81', ico_open => '0x82', ico_closed => '0x83',
  4840. ico_error => '0x84', ico_ok => '0x85', ico_info => '0x86', ico_newmsg => '0x87',
  4841. ico_svcmsg => '0x88'
  4842. );
  4843. my %disp_sounds = (
  4844. snd_off => '0xC0', snd_longlong => '0xC1', snd_longshort => '0xC2',
  4845. snd_long2short => '0xC3', snd_short => '0xC4', snd_shortshort => '0xC5',
  4846. snd_long => '0xC6'
  4847. );
  4848. my %disp_signals = (
  4849. sig_off => '0xF0', sig_red => '0xF1', sig_green => '0xF2', sig_orange => '0xF3'
  4850. );
  4851. # Parse command string
  4852. my @text = ('', '', '');
  4853. my @icon = ('', '', '');
  4854. my %conf = (sound => 'snd_off', signal => 'sig_off', repeat => 1, pause => 10);
  4855. foreach my $tok (split (',', $msg)) {
  4856. my ($par, $val) = split ('=', $tok);
  4857. next if (!defined ($val));
  4858. if ($par =~ /^text([1-3])$/) {
  4859. $text[$1-1] = substr ($val, 0, 12);
  4860. }
  4861. elsif ($par =~ /^icon([1-3])$/) {
  4862. $icon[$1-1] = $val;
  4863. }
  4864. elsif ($par =~ /^(sound|pause|repeat|signal)$/) {
  4865. $conf{$1} = $val;
  4866. }
  4867. }
  4868. my $cmd = '0x02,0x0A';
  4869. for (my $c=0; $c<3; $c++) {
  4870. if ($text[$c] ne '' || $icon[$c] ne '') {
  4871. $cmd .= ',0x12';
  4872. # Hex code
  4873. if ($text[$c] =~ /^0x[0-9A-F]{2}$/) {
  4874. $cmd .= ','.$text[$c];
  4875. }
  4876. # Predefined text code #0-9
  4877. elsif ($text[$c] =~ /^#([0-9])$/) {
  4878. $cmd .= sprintf (",0x8%1X", $1);
  4879. }
  4880. # Convert string to hex codes
  4881. else {
  4882. $text[$c] =~ s/\\_/ /g;
  4883. foreach my $ch (split ('', $text[$c])) {
  4884. $cmd .= sprintf (",0x%02X", ord ($ch));
  4885. }
  4886. }
  4887. # Icon
  4888. if ($icon[$c] ne '' && exists ($disp_icons{$icon[$c]})) {
  4889. $cmd .= ',0x13,'.$disp_icons{$icon[$c]};
  4890. }
  4891. }
  4892. $cmd .= ',0x0A';
  4893. }
  4894. # Sound
  4895. my $snd = $disp_sounds{snd_off};
  4896. $snd = $disp_sounds{$conf{sound}} if (exists ($disp_sounds{$conf{sound}}));
  4897. $cmd .= ',0x14,'.$snd.',0x1C';
  4898. # Repeat
  4899. my $rep = $conf{repeat} if ($conf{repeat} >= 0 && $conf{repeat} <= 15);
  4900. $rep = 1 if ($rep < 0);
  4901. $rep = 15 if ($rep > 15);
  4902. if ($rep == 0) {
  4903. $cmd .= ',0xDF';
  4904. }
  4905. else {
  4906. $cmd .= sprintf (",0x%02X", 0xD0+$rep-1);
  4907. }
  4908. $cmd .= ',0x1D';
  4909. # Pause
  4910. my $pause = $conf{pause};
  4911. $pause = 1 if ($pause < 1);
  4912. $pause = 160 if ($pause > 160);
  4913. $cmd .= sprintf (",0xE%1X,0x16", int(($pause-1)/10));
  4914. # Signal
  4915. my $sig = $disp_signals{sig_off};
  4916. $sig = $disp_signals{$conf{signal}} if (exists ($disp_signals{$conf{signal}}));
  4917. $cmd .= ','.$sig.',0x03';
  4918. return '"'.$cmd.'"';
  4919. }
  4920. ######################################################################
  4921. # Convert reference to string recursively
  4922. # Supports reference to ARRAY, HASH and SCALAR and scalar values.
  4923. ######################################################################
  4924. sub HMCCU_RefToString ($)
  4925. {
  4926. my ($r) = @_;
  4927. my $result = '';
  4928. if (ref ($r) eq 'ARRAY') {
  4929. $result .= "[\n";
  4930. foreach my $e (@$r) {
  4931. $result .= "," if ($result ne '[');
  4932. $result .= HMCCU_RefToString ($e);
  4933. }
  4934. $result .= "\n]";
  4935. }
  4936. elsif (ref ($r) eq 'HASH') {
  4937. $result .= "{\n";
  4938. foreach my $k (sort keys %$r) {
  4939. $result .= "," if ($result ne '{');
  4940. $result .= "$k=".HMCCU_RefToString ($r->{$k});
  4941. }
  4942. $result .= "\n}";
  4943. }
  4944. elsif (ref ($r) eq 'SCALAR') {
  4945. $result .= $$r;
  4946. }
  4947. else {
  4948. $result .= $r;
  4949. }
  4950. return $result;
  4951. }
  4952. ######################################################################
  4953. # Match string with regular expression considering illegal regular
  4954. # expressions. Return parameter e if regular expression is incorrect.
  4955. ######################################################################
  4956. sub HMCCU_ExprMatch ($$$)
  4957. {
  4958. my ($t, $r, $e) = @_;
  4959. my $x = eval { $t =~ /$r/ };
  4960. return $e if (!defined ($x));
  4961. return "$x" eq '' ? 0 : 1;
  4962. }
  4963. sub HMCCU_ExprNotMatch ($$$)
  4964. {
  4965. my ($t, $r, $e) = @_;
  4966. my $x = eval { $t !~ /$r/ };
  4967. return $e if (!defined ($x));
  4968. return "$x" eq '' ? 0 : 1;
  4969. }
  4970. ######################################################################
  4971. # Read duty cycles of interfaces 2001 and 2010 and update readings.
  4972. ######################################################################
  4973. sub HMCCU_GetDutyCycle ($)
  4974. {
  4975. my ($hash) = @_;
  4976. my $name = $hash->{NAME};
  4977. my $host = $hash->{host};
  4978. my $dc = 0;
  4979. my @rpcports = HMCCU_GetRPCPortList ($hash);
  4980. readingsBeginUpdate ($hash);
  4981. foreach my $port (@rpcports) {
  4982. next if ($port != 2001 && $port != 2010);
  4983. my $url = "http://$host:$port/";
  4984. my $rpcclient = RPC::XML::Client->new ($url);
  4985. my $response = $rpcclient->simple_request ("listBidcosInterfaces");
  4986. next if (!defined ($response) || ref($response) ne 'ARRAY');
  4987. foreach my $iface (@$response) {
  4988. next if (ref ($iface) ne 'HASH');
  4989. next if (!exists ($iface->{DUTY_CYCLE}));
  4990. $dc++;
  4991. my $type = exists ($iface->{TYPE}) ? $iface->{TYPE} : $HMCCU_RPC_NUMPORT{$port};
  4992. readingsBulkUpdate ($hash, "iface_addr_$dc", $iface->{ADDRESS});
  4993. readingsBulkUpdate ($hash, "iface_conn_$dc", $iface->{CONNECTED});
  4994. readingsBulkUpdate ($hash, "iface_type_$dc", $type);
  4995. readingsBulkUpdate ($hash, "iface_ducy_$dc", $iface->{DUTY_CYCLE});
  4996. }
  4997. }
  4998. readingsEndUpdate ($hash, 1);
  4999. return $dc;
  5000. }
  5001. ######################################################################
  5002. # Check if TCP port is reachable.
  5003. # Parameter timeout should be a multiple of 20 plus 5.
  5004. ######################################################################
  5005. sub HMCCU_TCPPing ($$$)
  5006. {
  5007. my ($addr, $port, $timeout) = @_;
  5008. if ($timeout > 0) {
  5009. my $t = time ();
  5010. while (time () < $t+$timeout) {
  5011. if (HMCCU_TCPConnect ($addr, $port)) {
  5012. sleep (30);
  5013. return 1;
  5014. }
  5015. sleep (20);
  5016. }
  5017. return 0;
  5018. }
  5019. else {
  5020. return HMCCU_TCPConnect ($addr, $port);
  5021. }
  5022. }
  5023. ######################################################################
  5024. # Check if TCP connection to specified host and port is possible.
  5025. ######################################################################
  5026. sub HMCCU_TCPConnect ($$)
  5027. {
  5028. my ($addr, $port) = @_;
  5029. my $socket = IO::Socket::INET->new (PeerAddr => $addr, PeerPort => $port);
  5030. if ($socket) {
  5031. close ($socket);
  5032. return 1;
  5033. }
  5034. return 0;
  5035. }
  5036. ######################################################################
  5037. # Substitute invalid characters in reading name.
  5038. # Substitution rules: ':' => '.', any other illegal character => '_'
  5039. ######################################################################
  5040. sub HMCCU_CorrectName ($)
  5041. {
  5042. my ($rn) = @_;
  5043. $rn =~ s/\:/\./g;
  5044. $rn =~ s/[^A-Za-z\d_\.-]+/_/g;
  5045. return $rn;
  5046. }
  5047. ######################################################################
  5048. # *** SUBPROCESS PART ***
  5049. ######################################################################
  5050. # Child process. Must be global to allow access by RPC callbacks
  5051. my $hmccu_child;
  5052. # Queue file
  5053. my %child_queue;
  5054. my $cpqueue = \%child_queue;
  5055. # Statistic data of child process
  5056. my %child_hash = (
  5057. "total", 0,
  5058. "writeerror", 0,
  5059. "EV", 0,
  5060. "ND", 0,
  5061. "DD", 0,
  5062. "RD", 0,
  5063. "RA", 0,
  5064. "UD", 0,
  5065. "IN", 0,
  5066. "EX", 0,
  5067. "SL", 0
  5068. );
  5069. my $cphash = \%child_hash;
  5070. ######################################################################
  5071. # Subprocess: Write event to parent process
  5072. ######################################################################
  5073. sub HMCCU_CCURPC_Write ($$)
  5074. {
  5075. my ($et, $msg) = @_;
  5076. my $name = $hmccu_child->{devname};
  5077. $cphash->{total}++;
  5078. $cphash->{$et}++;
  5079. HMCCU_QueueEnq ($cpqueue, $et."|".$msg);
  5080. }
  5081. ######################################################################
  5082. # Subprocess: Initialize RPC server. Return 1 on success.
  5083. ######################################################################
  5084. sub HMCCU_CCURPC_OnRun ($)
  5085. {
  5086. $hmccu_child = shift;
  5087. my $name = $hmccu_child->{devname};
  5088. my $serveraddr = $hmccu_child->{serveraddr};
  5089. my $serverport = $hmccu_child->{serverport};
  5090. my $callbackport = $hmccu_child->{callbackport};
  5091. my $queuefile = $hmccu_child->{queue};
  5092. my $clkey = "CB".$serverport;
  5093. my $ccurpc_server;
  5094. # Create, open and reset queue file
  5095. Log3 $name, 0, "CCURPC: $clkey Creating file queue $queuefile";
  5096. if (!HMCCU_QueueOpen ($cpqueue, $queuefile)) {
  5097. Log3 $name, 0, "CCURPC: $clkey Can't create queue";
  5098. return 0;
  5099. }
  5100. # Reset event queue
  5101. HMCCU_QueueReset ($cpqueue);
  5102. while (defined (HMCCU_QueueDeq ($cpqueue))) { }
  5103. # Create RPC server
  5104. Log3 $name, 0, "CCURPC: Initializing RPC server $clkey";
  5105. $ccurpc_server = RPC::XML::Server->new (port=>$callbackport);
  5106. if (!ref($ccurpc_server))
  5107. {
  5108. Log3 $name, 0, "CCURPC: Can't create RPC callback server on port $callbackport. Port in use?";
  5109. return 0;
  5110. }
  5111. else {
  5112. Log3 $name, 0, "CCURPC: Callback server created listening on port $callbackport";
  5113. }
  5114. # Callback for events
  5115. Log3 $name, 1, "CCURPC: $clkey Adding callback for events";
  5116. $ccurpc_server->add_method (
  5117. { name=>"event",
  5118. signature=> ["string string string string int","string string string string double","string string string string boolean","string string string string i4"],
  5119. code=>\&HMCCU_CCURPC_EventCB
  5120. }
  5121. );
  5122. # Callback for new devices
  5123. Log3 $name, 1, "CCURPC: $clkey Adding callback for new devices";
  5124. $ccurpc_server->add_method (
  5125. { name=>"newDevices",
  5126. signature=>["string string array"],
  5127. code=>\&HMCCU_CCURPC_NewDevicesCB
  5128. }
  5129. );
  5130. # Callback for deleted devices
  5131. Log3 $name, 1, "CCURPC: $clkey Adding callback for deleted devices";
  5132. $ccurpc_server->add_method (
  5133. { name=>"deleteDevices",
  5134. signature=>["string string array"],
  5135. code=>\&HMCCU_CCURPC_DeleteDevicesCB
  5136. }
  5137. );
  5138. # Callback for modified devices
  5139. Log3 $name, 1, "CCURPC: $clkey Adding callback for modified devices";
  5140. $ccurpc_server->add_method (
  5141. { name=>"updateDevice",
  5142. signature=>["string string string int"],
  5143. code=>\&HMCCU_CCURPC_UpdateDeviceCB
  5144. }
  5145. );
  5146. # Callback for replaced devices
  5147. Log3 $name, 1, "CCURPC: $clkey Adding callback for replaced devices";
  5148. $ccurpc_server->add_method (
  5149. { name=>"replaceDevice",
  5150. signature=>["string string string string"],
  5151. code=>\&HMCCU_CCURPC_ReplaceDeviceCB
  5152. }
  5153. );
  5154. # Callback for readded devices
  5155. Log3 $name, 1, "CCURPC: $clkey Adding callback for readded devices";
  5156. $ccurpc_server->add_method (
  5157. { name=>"replaceDevice",
  5158. signature=>["string string array"],
  5159. code=>\&HMCCU_CCURPC_ReaddDeviceCB
  5160. }
  5161. );
  5162. # Dummy implementation, always return an empty array
  5163. Log3 $name, 1, "CCURPC: $clkey Adding callback for list devices";
  5164. $ccurpc_server->add_method (
  5165. { name=>"listDevices",
  5166. signature=>["array string"],
  5167. code=>\&HMCCU_CCURPC_ListDevicesCB
  5168. }
  5169. );
  5170. # Enter server loop
  5171. HMCCU_CCURPC_Write ("SL", "$$|$clkey");
  5172. Log3 $name, 0, "CCURPC: $clkey Entering server loop";
  5173. $ccurpc_server->server_loop;
  5174. Log3 $name, 0, "CCURPC: $clkey Server loop terminated";
  5175. # Server loop exited by SIGINT
  5176. HMCCU_CCURPC_Write ("EX", "SHUTDOWN|$$|$clkey");
  5177. return 1;
  5178. }
  5179. ######################################################################
  5180. # Subprocess: Called when RPC server loop is terminated
  5181. ######################################################################
  5182. sub HMCCU_CCURPC_OnExit ()
  5183. {
  5184. # Output statistics
  5185. foreach my $et (sort keys %child_hash) {
  5186. Log3 $hmccu_child->{devname}, 2, "CCURPC: Eventcount $et = ".$cphash->{$et};
  5187. }
  5188. }
  5189. ######################################################################
  5190. # Subprocess: Callback for new devices
  5191. ######################################################################
  5192. sub HMCCU_CCURPC_NewDevicesCB ($$$)
  5193. {
  5194. my ($server, $cb, $a) = @_;
  5195. my $devcount = scalar (@$a);
  5196. my $name = $hmccu_child->{devname};
  5197. my $c = 0;
  5198. my $msg = '';
  5199. Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device specifications";
  5200. for my $dev (@$a) {
  5201. my $msg = '';
  5202. if ($dev->{ADDRESS} =~ /:[0-9]{1,2}$/) {
  5203. $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null";
  5204. }
  5205. else {
  5206. # Wired devices do not have a RX_MODE attribute
  5207. my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null';
  5208. $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|".
  5209. $dev->{FIRMWARE}."|".$rx;
  5210. }
  5211. HMCCU_CCURPC_Write ("ND", $msg);
  5212. }
  5213. return;
  5214. }
  5215. ######################################################################
  5216. # Subprocess: Callback for deleted devices
  5217. ######################################################################
  5218. sub HMCCU_CCURPC_DeleteDevicesCB ($$$)
  5219. {
  5220. my ($server, $cb, $a) = @_;
  5221. my $name = $hmccu_child->{devname};
  5222. my $devcount = scalar (@$a);
  5223. Log3 $name, 2, "CCURPC: $cb DeleteDevice received $devcount device addresses";
  5224. for my $dev (@$a) {
  5225. HMCCU_CCURPC_Write ("DD", $dev);
  5226. }
  5227. return;
  5228. }
  5229. ######################################################################
  5230. # Subprocess: Callback for modified devices
  5231. ######################################################################
  5232. sub HMCCU_CCURPC_UpdateDeviceCB ($$$$)
  5233. {
  5234. my ($server, $cb, $devid, $hint) = @_;
  5235. HMCCU_CCURPC_Write ("UD", $devid."|".$hint);
  5236. return;
  5237. }
  5238. ######################################################################
  5239. # Subprocess: Callback for replaced devices
  5240. ######################################################################
  5241. sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$)
  5242. {
  5243. my ($server, $cb, $devid1, $devid2) = @_;
  5244. HMCCU_CCURPC_Write ("RD", $devid1."|".$devid2);
  5245. return;
  5246. }
  5247. ######################################################################
  5248. # Subprocess: Callback for readded devices
  5249. ######################################################################
  5250. sub HMCCU_CCURPC_ReaddDevicesCB ($$$)
  5251. {
  5252. my ($server, $cb, $a) = @_;
  5253. my $name = $hmccu_child->{devname};
  5254. my $devcount = scalar (@$a);
  5255. Log3 $name, 2, "CCURPC: $cb ReaddDevice received $devcount device addresses";
  5256. for my $dev (@$a) {
  5257. HMCCU_CCURPC_Write ("RA", $dev);
  5258. }
  5259. return;
  5260. }
  5261. ######################################################################
  5262. # Subprocess: Callback for handling CCU events
  5263. ######################################################################
  5264. sub HMCCU_CCURPC_EventCB ($$$$$)
  5265. {
  5266. my ($server, $cb, $devid, $attr, $val) = @_;
  5267. my $name = $hmccu_child->{devname};
  5268. HMCCU_CCURPC_Write ("EV", $devid."|".$attr."|".$val);
  5269. if (($cphash->{EV} % 500) == 0) {
  5270. Log3 $name, 3, "CCURPC: $cb Received 500 events from CCU since last check";
  5271. my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX');
  5272. my $msg = '';
  5273. foreach my $stkey (@stkeys) {
  5274. $msg .= '|' if ($msg ne '');
  5275. $msg .= $cphash->{$stkey};
  5276. }
  5277. HMCCU_CCURPC_Write ("ST", $msg);
  5278. }
  5279. # Never remove this statement!
  5280. return;
  5281. }
  5282. ######################################################################
  5283. # Subprocess: Callback for list devices
  5284. ######################################################################
  5285. sub HMCCU_CCURPC_ListDevicesCB ($$)
  5286. {
  5287. my ($server, $cb) = @_;
  5288. my $name = $hmccu_child->{devname};
  5289. $cb = "unknown" if (!defined ($cb));
  5290. Log3 $name, 1, "CCURPC: $cb ListDevices. Sending init to HMCCU";
  5291. HMCCU_CCURPC_Write ("IN", "INIT|1|$cb");
  5292. return RPC::XML::array->new();
  5293. }
  5294. 1;
  5295. =pod
  5296. =item device
  5297. =item summary provides interface between FHEM and Homematic CCU2
  5298. =begin html
  5299. <a name="HMCCU"></a>
  5300. <h3>HMCCU</h3>
  5301. <ul>
  5302. The module provides an interface between FHEM and a Homematic CCU2. HMCCU is the
  5303. I/O device for the client devices HMCCUDEV and HMCCUCHN. The module requires the
  5304. additional Perl modules IO::File, RPC::XML::Client, RPC::XML::Server and SubProcess
  5305. (part of FHEM).
  5306. </br></br>
  5307. <a name="HMCCUdefine"></a>
  5308. <b>Define</b><br/><br/>
  5309. <ul>
  5310. <code>define &lt;name&gt; HMCCU &lt;HostOrIP&gt; [&lt;ccu-number&gt;] [waitforccu=&lt;timeout&gt;]</code>
  5311. <br/><br/>
  5312. Example:<br/>
  5313. <code>define myccu HMCCU 192.168.1.10</code>
  5314. <br/><br/>
  5315. The parameter <i>HostOrIP</i> is the hostname or IP address of a Homematic CCU2. If you have
  5316. more than one CCU you can specifiy a unique CCU number with parameter <i>ccu-number</i>. With
  5317. option <i>waitforccu</i> HMCCU will wait for the specified time if CCU is not reachable.
  5318. Parameter <i>timeout</i> should be a multiple of 20 in seconds. Warning: This option could
  5319. block the start of FHEM for <i>timeout</i> seconds.
  5320. <br/>
  5321. For automatic update of Homematic device datapoints and FHEM readings one have to:
  5322. <br/><br/>
  5323. <ul>
  5324. <li>Define used RPC interfaces with attribute 'rpcinterfaces'</li>
  5325. <li>Start RPC servers with command 'set rpcserver on'</li>
  5326. <li>Optionally enable automatic start of RPC servers with attribute 'rpcserver'</li>
  5327. </ul><br/>
  5328. Than start with the definition of client devices using modules HMCCUDEV (CCU devices)
  5329. and HMCCUCHN (CCU channels) or with command 'get devicelist create'.<br/>
  5330. Maybe it's helpful to set the following FHEM standard attributes for the HMCCU I/O
  5331. device:<br/><br/>
  5332. <ul>
  5333. <li>Shortcut for RPC server control: eventMap /rpcserver on:on/rpcserver off:off/</li>
  5334. </ul>
  5335. </ul>
  5336. <br/>
  5337. <a name="HMCCUset"></a>
  5338. <b>Set</b><br/><br/>
  5339. <ul>
  5340. <li><b>set &lt;name&gt; cleardefaults</b><br/>
  5341. Clear default attributes imported from file.
  5342. </li><br/>
  5343. <li><b>set &lt;name&gt; datapoint {&lt;[ccu:]ccuobject&gt;|&lt;hmccu:fhemobject&gt;}.
  5344. &lt;datapoint&gt; &lt;value&gt;</b><br/>
  5345. Set datapoint of CCU channel in "raw" mode. The value is not scaled or substituted. If
  5346. target object is preceded by string "hmccu:" the following parameter <i>fhemobject</i>
  5347. must be a FHEM device of type HMCCUDEV or HMCCUCHN. If device type is HMCCUDEV the device
  5348. name must be followed by a ':' and a valid channel number.<br/><br/>
  5349. Examples:<br/>
  5350. <code>set d_ccu datapoint ABC1234567:1.STATE true</code><br/>
  5351. <code>set d_ccu datapoint hmccu:mychndevice.STATE true</code><br/>
  5352. <code>set d_ccu datapoint hmccu:mydevdevice:1.STATE true</code>
  5353. </li><br/>
  5354. <li><b>set &lt;name&gt; defaults</b><br/>
  5355. Set default attributes for I/O device.
  5356. </li><br/>
  5357. <li><b>set &lt;name&gt; delete &lt;ccuobject&gt; [&lt;objecttype&gt;]</b><br/>
  5358. Delete object in CCU. Default object type is OT_VARDP. Valid object types are<br/>
  5359. OT_DEVICE=device, OT_VARDP=variable.
  5360. </li><br/>
  5361. <li><b>set &lt;name&gt; execute &lt;program&gt;</b><br/>
  5362. Execute a CCU program.
  5363. <br/><br/>
  5364. Example:<br/>
  5365. <code>set d_ccu execute PR-TEST</code>
  5366. </li><br/>
  5367. <li><b>set &lt;name&gt; hmscript {&lt;script-file&gt;|'!'&lt;function&gt;|'['&lt;code&gt;']'} [dump]
  5368. [&lt;parname&gt;=&lt;value&gt; [...]]</b><br/>
  5369. Execute Homematic script on CCU. If script code contains parameter in format $parname
  5370. they are substituted by corresponding command line parameters <i>parname</i>.<br/>
  5371. If output of script contains lines in format Object=Value readings in existing
  5372. corresponding FHEM devices will be set. <i>Object</i> can be the name of a CCU system
  5373. variable or a valid channel and datapoint specification. Readings for system variables
  5374. are set in the I/O device. Datapoint related readings are set in client devices. If option
  5375. 'dump' is specified the result of script execution is displayed in FHEM web interface.
  5376. Execute command without parameters will list available script functions.
  5377. </li><br/>
  5378. <li><b>set &lt;name&gt; importdefaults &lt;filename&gt;</b><br/>
  5379. Import default attributes from file.
  5380. </li><br/>
  5381. <li><b>set &lt;name&gt; rpcserver {on | off | restart}</b><br/>
  5382. Start, stop or restart RPC server(s). This command executed with option 'on'
  5383. will fork a RPC server process for each RPC interface defined in attribute 'rpcinterfaces'.
  5384. Until operation is completed only a few set/get commands are available and you
  5385. may get the error message 'CCU busy'.
  5386. </li><br/>
  5387. <li><b>set &lt;name&gt; var &lt;variable&gt; &lt;Value&gt;</b><br/>
  5388. Set CCU system variable value. Special characters \_ in <i>value</i> are
  5389. substituted by blanks.
  5390. </li>
  5391. </ul>
  5392. <br/>
  5393. <a name="HMCCUget"></a>
  5394. <b>Get</b><br/><br/>
  5395. <ul>
  5396. <li><b>get &lt;name&gt; aggregation {&lt;rule&gt;|all}</b><br/>
  5397. Process aggregation rule defined with attribute ccuaggregate.
  5398. </li><br/>
  5399. <li><b>get &lt;name&gt; configdesc {&lt;device&gt;|&lt;channel&gt;}</b><br/>
  5400. Get configuration parameter description of CCU device or channel (similar
  5401. to device settings in CCU). Not every CCU device or channel provides a configuration
  5402. parameter description. So result may be empty.
  5403. </li><br/>
  5404. <li><b>get &lt;name&gt; defaults</b><br/>
  5405. List device types and channels with default attributes available.
  5406. </li><br/>
  5407. <li><b>get &lt;name&gt; deviceinfo &lt;device-name&gt; [{State | <u>Value</u>}]</b><br/>
  5408. List device channels and datapoints. If option 'State' is specified the device is
  5409. queried directly. Otherwise device information from CCU is listed.
  5410. </li><br/>
  5411. <li><b>get &lt;name&gt; devicelist [dump]</b><br/>
  5412. Read list of devices and channels from CCU. This command is executed automatically
  5413. after the definition of an I/O device. It must be executed manually after
  5414. module HMCCU is reloaded or after devices have changed in CCU (added, removed or
  5415. renamed). With option 'dump' devices are displayed in browser window. If a RPC
  5416. server is running HMCCU will raise events "<i>count</i> devices added in CCU" or
  5417. "<i>count</i> devices deleted in CCU". It's recommended to set up a notification
  5418. which reacts with execution of command 'get devicelist' on these events.
  5419. </li><br/>
  5420. <li><b>get &lt;name&gt; devicelist create &lt;devexp&gt; [t={chn|<u>dev</u>|all}]
  5421. [p=&lt;prefix&gt;] [s=&lt;suffix&gt;] [f=&lt;format&gt;] [defattr] [duplicates]
  5422. [save] [&lt;attr&gt;=&lt;value&gt; [...]]</b><br/>
  5423. With option 'create' HMCCU will automatically create client devices for all CCU devices
  5424. and channels matching specified regular expression. With option t=chn or t=dev (default)
  5425. the creation of devices is limited to CCU channels or devices.<br/>
  5426. Optionally a <i>prefix</i> and/or a
  5427. <i>suffix</i> for the FHEM device name can be specified. The parameter <i>format</i>
  5428. defines a template for the FHEM device names. Prefix, suffix and format can contain
  5429. format identifiers which are substituted by corresponding values of the CCU device or
  5430. channel: %n = CCU object name (channel or device), %d = CCU device name, %a = CCU address.
  5431. In addition a list of default attributes for the created client devices can be specified.
  5432. If option 'defattr' is specified HMCCU tries to set default attributes for device.
  5433. With option 'duplicates' HMCCU will overwrite existing devices and/or create devices
  5434. for existing device addresses. Option 'save' will save FHEM config after device definition.
  5435. </li><br/>
  5436. <li><b>get &lt;name&gt; dump {datapoints|devtypes} [&lt;filter&gt;]</b><br/>
  5437. Dump all Homematic devicetypes or all devices including datapoints currently
  5438. defined in FHEM.
  5439. </li><br/>
  5440. <li><b>get &lt;name&gt; dutycycle</b><br/>
  5441. Read CCU interface and gateway information. For each interface/gateway the following
  5442. information is stored in readings:<br/>
  5443. iface_addr_n = interface address<br/>
  5444. iface_type_n = interface type<br/>
  5445. iface_conn_n = interface connection state (1=connected, 0=disconnected)<br/>
  5446. iface_ducy_n = duty cycle of interface (0-100)
  5447. </li><br/>
  5448. <li><b>get &lt;name&gt; exportdefaults &lt;filename&gt;</b><br/>
  5449. Export default attributes into file.
  5450. </li><br/>
  5451. <li><b>get &lt;name&gt; firmware</b><br/>
  5452. Get available firmware downloads from eq-3.de. List FHEM devices with current and available
  5453. firmware version. Firmware versions are only displayed after RPC server has been started.
  5454. </li><br/>
  5455. <li><b>get &lt;name&gt; parfile [&lt;parfile&gt;]</b><br/>
  5456. Get values of all channels / datapoints specified in <i>parfile</i>. The parameter
  5457. <i>parfile</i> can also be defined as an attribute. The file must contain one channel /
  5458. definition per line.
  5459. <br/><br/>
  5460. The syntax of Parfile entries is:
  5461. <br/><br/>
  5462. {[&lt;interface&gt;.]&lt;channel-address&gt; | &lt;channel-name&gt;}
  5463. [.&lt;datapoint-expr&gt;] [&lt;subst-rules&gt;]
  5464. <br/><br/>
  5465. Empty lines or lines starting with a # are ignored.
  5466. </li><br/>
  5467. <li><b>get &lt;name&gt; rpcstate</b><br/>
  5468. Check if RPC server process is running.
  5469. </li><br/>
  5470. <li><b>get &lt;name&gt; update [&lt;devexp&gt; [{State | <u>Value</u>}]]</b><br/>
  5471. Update all datapoints / readings of client devices with <u>FHEM device name</u>(!) matching
  5472. <i>devexp</i>. With option 'State' all CCU devices are queried directly. This can be
  5473. time consuming.
  5474. </li><br/>
  5475. <li><b>get &lt;name&gt; updateccu [&lt;devexp&gt; [{State | <u>Value</u>}]]</b><br/>
  5476. Update all datapoints / readings of client devices with <u>CCU device name</u>(!) matching
  5477. <i>devexp</i>. With option 'State' all CCU devices are queried directly. This can be
  5478. time consuming.
  5479. </li><br/>
  5480. <li><b>get &lt;name&gt; vars &lt;regexp&gt;</b><br/>
  5481. Get CCU system variables matching <i>regexp</i> and store them as readings.
  5482. </li>
  5483. </ul>
  5484. <br/>
  5485. <a name="HMCCUattr"></a>
  5486. <b>Attributes</b><br/>
  5487. <br/>
  5488. <ul>
  5489. <li><b>ccuackstate {0 | <u>1</u>}</b><br/>
  5490. If set to 1 state will be set to result of command (i.e. 'OK').
  5491. </li><br/>
  5492. <li><b>ccuaggregate &lt;rule&gt;[;...]</b><br/>
  5493. Define aggregation rules for client device readings. With an aggregation rule
  5494. it's easy to detect if some or all client device readings are set to a specific
  5495. value, i.e. detect all devices with low battery or detect all open windows.<br/>
  5496. Aggregation rules are automatically executed as a reaction on reading events of
  5497. HMCCU client devices. An aggregation rule consists of several parameters separated
  5498. by comma:<br/><br/>
  5499. <ul>
  5500. <li><b>name:&lt;rule-name&gt;</b><br/>
  5501. Name of aggregation rule</li>
  5502. <li><b>filter:{name|alias|group|room|type}=&lt;incl-expr&gt;[!&lt;excl-expr&gt;]</b><br/>
  5503. Filter criteria, i.e. "type=^HM-Sec-SC.*"</li>
  5504. <li><b>read:&lt;read-expr&gt;</b><br/>
  5505. Expression for reading names, i.e. "STATE"</li>
  5506. <li><b>if:{any|all|min|max|sum|avg|lt|gt|le|ge}=&lt;value&gt;</b><br/>
  5507. Condition, i.e. "any=open" or initial value, i.e. max=0</li>
  5508. <li><b>else:&lt;value&gt;</b><br/>
  5509. Complementary value, i.e. "closed"</li>
  5510. <li><b>prefix:{&lt;text&gt;|RULE}</b><br/>
  5511. Prefix for reading names with aggregation results</li>
  5512. <li><b>coll:{&lt;attribute&gt;|NAME}[!&lt;default-text&gt;]</b><br/>
  5513. Attribute of matching devices stored in aggregation results. Default text in case
  5514. of no matching devices found is optional.</li>
  5515. <li><b>html:&lt;template-file&gt;</b><br/>
  5516. Create HTML code with matching devices.</li>
  5517. </ul><br/>
  5518. Aggregation results will be stored in readings <i>prefix</i>count, <i>prefix</i>list,
  5519. <i>prefix</i>match, <i>prefix</i>state and <i>prefix</i>table.<br/><br/>
  5520. Format of a line in <i>template-file</i> is &lt;keyword&gt;:&lt;html-code&gt;. See
  5521. FHEM Wiki for an example. Valid keywords are:<br/><br/>
  5522. <ul>
  5523. <li><b>begin-html</b>: Start of html code.</li>
  5524. <li><b>begin-table</b>: Start of table (i.e. the table header)</li>
  5525. <li><b>row-odd</b>: HTML code for odd lines. A tag &lt;reading/&gt is replaced by a matching device.</li>
  5526. <li><b>row-even</b>: HTML code for event lines.</li>
  5527. <li><b>end-table</b>: End of table.</li>
  5528. <li><b>default</b>: HTML code for no matches.</li>
  5529. <li><b>end-html</b>: End of html code.</li>
  5530. </ul><br/>
  5531. Example: Find open windows<br/>
  5532. name=lock,filter:type=^HM-Sec-SC.*,read:STATE,if:any=open,else:closed,prefix:lock_,coll:NAME!All windows closed<br/><br/>
  5533. Example: Find devices with low batteries. Generate reading in HTML format.<br/>
  5534. name=battery,filter:name=.*,read:(LOWBAT|LOW_BAT),if:any=yes,else:no,prefix:batt_,coll:NAME!All batteries OK,html:/home/battery.cfg<br/>
  5535. </li><br/>
  5536. <li><b>ccudef-hmstatevals &lt;subst-rule[;...]&gt;</b><br/>
  5537. Set global rules for calculation of reading hmstate.
  5538. </li><br/>
  5539. <li><b>ccudef-readingfilter &lt;filter-rule[;...]&gt;</b><br/>
  5540. Set global reading/datapoint filter. This filter is added to the filter specified by
  5541. client device attribute 'ccureadingfilter'.
  5542. </li><br/>
  5543. <li><b>ccudef-readingformat {name | address | <u>datapoint</u> | namelc | addresslc |
  5544. datapointlc}</b><br/>
  5545. Set global reading format. This format is the default for all readings except readings
  5546. of virtual device groups.
  5547. </li><br/>
  5548. <li><b>ccudef-readingname &lt;old-readingname-expr&gt;:[+]&lt;new-readingname&gt;
  5549. [;...]</b><br/>
  5550. Set global rules for reading name substitution. These rules are added to the rules
  5551. specified by client device attribute 'ccureadingname'.
  5552. </li><br/>
  5553. <li><b>ccudef-substitute &lt;subst-rule&gt;[;...]</b><br/>
  5554. Set global substitution rules for datapoint value. These rules are added to the rules
  5555. specified by client device attribute 'substitute'.
  5556. </li><br/>
  5557. <li><b>ccudefaults &lt;filename&gt;</b><br/>
  5558. Load default attributes for HMCCUCHN and HMCCUDEV devices from specified file. Best
  5559. practice for creating a custom default attribute file is by exporting predefined default
  5560. attributes from HMCCU with command 'get exportdefaults'.
  5561. </li><br/>
  5562. <li><b>ccuflags {extrpc, <u>intrpc</u>}</b><br/>
  5563. Control RPC server process and datapoint validation:<br/>
  5564. intrpc - Use internal RPC server. This is the default.<br/>
  5565. extrpc - Use external RPC server provided by module HMCCURPC. If no HMCCURPC device
  5566. exists HMCCU will create one after command 'set rpcserver on'.<br/>
  5567. dptnocheck - Do not check within set or get commands if datapoint is valid<br/>
  5568. </li><br/>
  5569. <li><b>ccuget {State | <u>Value</u>}</b><br/>
  5570. Set read access method for CCU channel datapoints. Method 'State' is slower than
  5571. 'Value' because each request is sent to the device. With method 'Value' only CCU
  5572. is queried. Default is 'Value'.
  5573. </li><br/>
  5574. <li><b>ccureadings {0 | <u>1</u>}</b><br/>
  5575. If set to 1 values read from CCU will be stored as readings. Otherwise output
  5576. is displayed in browser window.
  5577. </li><br/>
  5578. <li><b>parfile &lt;filename&gt;</b><br/>
  5579. Define parameter file for command 'get parfile'.
  5580. </li><br/>
  5581. <li><b>rpcdevice &lt;devicename&gt;</b><br/>
  5582. Specify name of external RPC device of type HMCCURPC.
  5583. </li><br/>
  5584. <li><b>rpcinterfaces &lt;interface&gt;[,...]</b><br/>
  5585. Specify list of CCU RPC interfaces. HMCCU will register a RPC server for each interface.
  5586. Valid interfaces are:<br/><br/>
  5587. <ul>
  5588. <li>BidCos-Wired (Port 2000)</li>
  5589. <li>BidCos-RF (Port 2001)</li>
  5590. <li>Homegear (Port 2003)</li>
  5591. <li>HmIP-RF (Port 2010)</li>
  5592. <li>CUxD (Port 8701)</li>
  5593. <li>VirtualDevice (Port 9292)</li>
  5594. </ul>
  5595. </li><br/>
  5596. <li><b>rpcinterval &lt;Seconds&gt;</b><br/>
  5597. Specifiy how often RPC queue is read. Default is 5 seconds.
  5598. </li><br/>
  5599. <li><b>rpcport &lt;value[,...]&gt;</b><br/>
  5600. Deprecated, use 'rpcinterfaces' instead. Specify list of RPC ports on CCU. Default is
  5601. 2001. Valid RPC ports are:<br/><br/>
  5602. <ul>
  5603. <li>2000 = Wired components</li>
  5604. <li>2001 = BidCos-RF (wireless 868 MHz components with BidCos protocol)</li>
  5605. <li>2003 = Homegear (experimental)</li>
  5606. <li>2010 = HM-IP (wireless 868 MHz components with IPv6 protocol)</li>
  5607. <li>8701 = CUxD (only supported with external RPC server HMCCURPC)</li>
  5608. <li>9292 = CCU group devices (especially heating groups)</li>
  5609. </ul>
  5610. </li><br/>
  5611. <li><b>rpcqueue &lt;queue-file&gt;</b><br/>
  5612. Specify name of RPC queue file. This parameter is only a prefix (including the
  5613. pathname) for the queue files with extension .idx and .dat. Default is
  5614. /tmp/ccuqueue. If FHEM is running on a SD card it's recommended that the queue
  5615. files are placed on a RAM disk.
  5616. </li><br/>
  5617. <li><b>rpcserver {on | <u>off</u>}</b><br/>
  5618. Specify if RPC server is automatically started on FHEM startup.
  5619. </li><br/>
  5620. <li><b>rpcserveraddr &lt;ip-or-name&gt;</b><br/>
  5621. Specify network interface by IP address or DNS name where RPC server should listen
  5622. on. By default HMCCU automatically detects the IP address. This attribute should be used
  5623. if the FHEM server has more than one network interface.
  5624. </li><br/>
  5625. <li><b>rpcserverport &lt;base-port&gt;</b><br/>
  5626. Specify base port for RPC server. The real listening port of an RPC server is
  5627. calculated by the formula: base-port + rpc-port + (10 * ccu-number). Default
  5628. value for <i>base-port</i> is 5400.<br/>
  5629. The value ccu-number is only relevant if more than one CCU is connected to FHEM.
  5630. Example: If <i>base-port</i> is 5000, protocol is BidCos (rpc-port 2001) and only
  5631. one CCU is connected the resulting RPC server port is 5000+2001+(10*0) = 7001.
  5632. </li><br/>
  5633. <li><b>substitute &lt;subst-rule&gt;:&lt;substext&gt;[,...]</b><br/>
  5634. Define substitions for datapoint values. Syntax of <i>subst-rule</i> is<br/><br/>
  5635. [[&lt;channelno.&gt;]&lt;datapoint&gt;[,...]!]&lt;{#n1-m1|regexp1}&gt;:&lt;text1&gt;[,...]
  5636. <br/>
  5637. Substitutions for parfile values must be specified in parfiles.
  5638. </li><br/>
  5639. <li><b>stripchar &lt;character&gt;</b><br/>
  5640. Strip the specified character from variable or device name in set commands. This
  5641. is useful if a variable should be set in CCU using the reading with trailing colon.
  5642. </li>
  5643. </ul>
  5644. </ul>
  5645. =end html
  5646. =cut