88_HMCCU.pm 260 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140
  1. ##############################################################################
  2. #
  3. # 88_HMCCU.pm
  4. #
  5. # $Id: 88_HMCCU.pm 17693 2018-11-06 07:51:56Z zap $
  6. #
  7. # Version 4.3.007
  8. #
  9. # Module for communication between FHEM and Homematic CCU2/3.
  10. #
  11. # Supports BidCos-RF, BidCos-Wired, HmIP-RF, virtual CCU channels,
  12. # CCU group devices, HomeGear, CUxD, Osram Lightify, Homematic Virtual Layer
  13. # and Philips Hue (not tested)
  14. #
  15. # (c) 2018 by zap (zap01 <at> t-online <dot> de)
  16. #
  17. ##############################################################################
  18. #
  19. # define <name> HMCCU <hostname_or_ip_of_ccu> [ccunumber] [waitforccu=<seconds>]
  20. #
  21. # set <name> ackmessages
  22. # set <name> cleardefaults
  23. # set <name> defaults
  24. # set <name> delete <name> [{ OT_VARDP | OT_DEVICE }]
  25. # set <name> execute <ccu_program>
  26. # set <name> importdefaults <filename>
  27. # set <name> hmscript {<scriptfile>|!<function>|'['<code>']'} [dump] [<parname>=<value> [...]]
  28. # set <name> rpcregister [{all|<interface>}]
  29. # set <name> rpcserver {on|off|restart}
  30. # set <name> var [<type>] <name> <value> [<parameter>=<value> [...]]
  31. #
  32. # get <name> aggregation {<rule>|all}
  33. # get <name> configdesc {<device>|<channel>}
  34. # get <name> defaults
  35. # get <name> deviceinfo <device>
  36. # get <name> devicelist [dump]
  37. # get <name> devicelist create <devexp> [t={chn|dev|all}] [s=<suffix>] [p=<prefix>] [f=<format>]
  38. # [defattr] [duplicates] [save] [<attr>=<val> [...]]}]
  39. # get <name> dump {devtypes|datapoints} [<filter>]
  40. # get <name> dutycycle
  41. # get <name> exportdefaults {filename} [csv] [all]
  42. # get <name> firmware [{type-expr}|full]
  43. # get <name> parfile [<parfile>]
  44. # get <name> rpcevents
  45. # get <name> rpcstate
  46. # get <name> update [<fhemdevexp> [{ State | Value }]]
  47. # get <name> updateccu [<devexp> [{ State | Value }]]
  48. # get <name> vars <regexp>
  49. #
  50. # attr <name> ccuaggregate <rules>
  51. # attr <name> ccudef-hmstatevals <subst_rules>
  52. # attr <name> ccudef-readingfilter <filter_rule>
  53. # attr <name> ccudef-readingname <rules>
  54. # attr <name> ccudef-substitute <subst_rule>
  55. # attr <name> ccudefaults <filename>
  56. # attr <name> ccuflags { intrpc,extrpc,procrpc,dptnocheck,noagg,logEvents,noReadings,nonBlocking }
  57. # attr <name> ccuget { State | Value }
  58. # attr <name> ccuReqTimeout <seconds>
  59. # attr <name> ccuGetVars <seconds>[:<pattern>]
  60. # attr <name> parfile <parfile>
  61. # attr <name> rpcevtimeout <seconds>
  62. # attr <name> rpcinterfaces { BidCos-Wired, BidCos-RF, HmIP-RF, VirtualDevices, Homegear, HVL }
  63. # attr <name> rpcinterval <seconds>
  64. # attr <name> rpcport <ccu_rpc_port>
  65. # attr <name> rpcqueue <file>
  66. # attr <name> rpcserver { on | off }
  67. # attr <name> rpcserveraddr <ip-or-name>
  68. # attr <name> rpcserverport <base_port>
  69. # attr <name> rpctimeout <read>[,<write>]
  70. # attr <name> stripchar <character>
  71. # attr <name> stripnumber [<datapoint-expr!]{-<digits>|0|1|2}[;...]
  72. # attr <name> substitute <subst_rule>
  73. #
  74. # filter_rule := channel-regexp!datapoint-regexp[;...]
  75. # subst_rule := [[channel.]datapoint[,...]!]<regexp>:<subtext>[,...][;...]
  76. ##############################################################################
  77. # Verbose levels:
  78. #
  79. # 0 = Log start/stop and initialization messages
  80. # 1 = Log errors
  81. # 2 = Log counters and warnings
  82. # 3 = Log events and runtime information
  83. ##############################################################################
  84. package main;
  85. no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  86. use strict;
  87. use warnings;
  88. # use Data::Dumper;
  89. # use Time::HiRes qw(usleep);
  90. use IO::File;
  91. use Fcntl 'SEEK_END', 'SEEK_SET', 'O_CREAT', 'O_RDWR';
  92. use RPC::XML::Client;
  93. use RPC::XML::Server;
  94. use SetExtensions;
  95. use SubProcess;
  96. use HMCCUConf;
  97. # Import configuration data
  98. my $HMCCU_CHN_DEFAULTS = \%HMCCUConf::HMCCU_CHN_DEFAULTS;
  99. my $HMCCU_DEV_DEFAULTS = \%HMCCUConf::HMCCU_DEV_DEFAULTS;
  100. my $HMCCU_SCRIPTS = \%HMCCUConf::HMCCU_SCRIPTS;
  101. # Custom configuration data
  102. my %HMCCU_CUST_CHN_DEFAULTS;
  103. my %HMCCU_CUST_DEV_DEFAULTS;
  104. # HMCCU version
  105. my $HMCCU_VERSION = '4.3.007';
  106. # Default RPC port (BidCos-RF)
  107. my $HMCCU_RPC_PORT_DEFAULT = 2001;
  108. my $HMCCU_RPC_INTERFACE_DEFAULT = 'BidCos-RF';
  109. # Constants and default values
  110. my $HMCCU_MAX_IOERRORS = 100;
  111. my $HMCCU_MAX_QUEUESIZE = 500;
  112. my $HMCCU_TIME_WAIT = 100000;
  113. my $HMCCU_TIME_TRIGGER = 10;
  114. my $HMCCU_TIMEOUT_CONNECTION = 10;
  115. my $HMCCU_TIMEOUT_WRITE = 0.001;
  116. my $HMCCU_TIMEOUT_ACCEPT = 1;
  117. my $HMCCU_TIMEOUT_EVENT = 600;
  118. my $HMCCU_STATISTICS = 500;
  119. my $HMCCU_TIMEOUT_REQUEST = 4;
  120. # RPC port name by port number
  121. my %HMCCU_RPC_NUMPORT = (
  122. 2000 => 'BidCos-Wired', 2001 => 'BidCos-RF', 2010 => 'HmIP-RF', 9292 => 'VirtualDevices',
  123. 2003 => 'Homegear', 8701 => 'CUxD', 7000 => 'HVL'
  124. );
  125. # RPC port number by port name
  126. my %HMCCU_RPC_PORT = (
  127. 'BidCos-Wired', 2000, 'BidCos-RF', 2001, 'HmIP-RF', 2010, 'VirtualDevices', 9292,
  128. 'Homegear', 2003, 'CUxD', 8701, 'HVL', 7000
  129. );
  130. # RPC flags
  131. my %HMCCU_RPC_FLAG = (
  132. 2000 => 'forceASCII', 2001 => 'forceASCII', 2003 => '_', 2010 => '_',
  133. 7000 => 'forceInit', 8701 => 'forceInit', 9292 => '_'
  134. );
  135. # Initial intervals for registration of RPC callbacks and reading RPC queue
  136. #
  137. # X = Start RPC server
  138. # X+HMCCU_INIT_INTERVAL1 = Register RPC callback
  139. # X+HMCCU_INIT_INTERVAL2 = Read RPC Queue
  140. #
  141. my $HMCCU_INIT_INTERVAL0 = 12;
  142. my $HMCCU_INIT_INTERVAL1 = 7;
  143. my $HMCCU_INIT_INTERVAL2 = 5;
  144. # Default values for delayed initialization during FHEM startup
  145. my $HMCCU_CCU_PING_TIMEOUT = 1;
  146. my $HMCCU_CCU_BOOT_DELAY = 180;
  147. my $HMCCU_CCU_DELAYED_INIT = 59;
  148. my $HMCCU_CCU_RPC_OFFSET = 20;
  149. # Number of arguments in RPC events
  150. my %rpceventargs = (
  151. "EV", 3, # Datapoint value updated
  152. "ND", 6, # New device created
  153. "DD", 1, # Device deleted
  154. "RD", 2, # Device renamed
  155. "RA", 1, # Device readded
  156. "UD", 2, # Device updated
  157. "IN", 3, # RPC init
  158. "EX", 3, # Exit RPC server
  159. "SL", 2, # RPC server loop
  160. "ST", 10 # Status
  161. );
  162. # Datapoint operations
  163. my $HMCCU_OPER_READ = 1;
  164. my $HMCCU_OPER_WRITE = 2;
  165. my $HMCCU_OPER_EVENT = 4;
  166. # Datapoint types
  167. my $HMCCU_TYPE_BINARY = 2;
  168. my $HMCCU_TYPE_FLOAT = 4;
  169. my $HMCCU_TYPE_INTEGER = 16;
  170. my $HMCCU_TYPE_STRING = 20;
  171. # Flags for CCU object specification
  172. my $HMCCU_FLAG_NAME = 1;
  173. my $HMCCU_FLAG_CHANNEL = 2;
  174. my $HMCCU_FLAG_DATAPOINT = 4;
  175. my $HMCCU_FLAG_ADDRESS = 8;
  176. my $HMCCU_FLAG_INTERFACE = 16;
  177. my $HMCCU_FLAG_FULLADDR = 32;
  178. # Valid flag combinations
  179. my $HMCCU_FLAGS_IACD = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS |
  180. $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  181. my $HMCCU_FLAGS_IAC = $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL;
  182. my $HMCCU_FLAGS_ACD = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  183. my $HMCCU_FLAGS_AC = $HMCCU_FLAG_ADDRESS | $HMCCU_FLAG_CHANNEL;
  184. my $HMCCU_FLAGS_ND = $HMCCU_FLAG_NAME | $HMCCU_FLAG_DATAPOINT;
  185. my $HMCCU_FLAGS_NC = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL;
  186. my $HMCCU_FLAGS_NCD = $HMCCU_FLAG_NAME | $HMCCU_FLAG_CHANNEL | $HMCCU_FLAG_DATAPOINT;
  187. # Flags for address/name checks
  188. my $HMCCU_FL_STADDRESS = 1;
  189. my $HMCCU_FL_NAME = 2;
  190. my $HMCCU_FL_EXADDRESS = 4;
  191. my $HMCCU_FL_ADDRESS = 5;
  192. my $HMCCU_FL_ALL = 7;
  193. # Default values
  194. my $HMCCU_DEF_HMSTATE = '^0\.UNREACH!(1|true):unreachable;^[0-9]\.LOW_?BAT!(1|true):warn_battery';
  195. # Placeholder for external addresses (i.e. HVL)
  196. my $HMCCU_EXT_ADDR = 'ZZZ0000000';
  197. # Binary RPC data types
  198. my $BINRPC_INTEGER = 1;
  199. my $BINRPC_BOOL = 2;
  200. my $BINRPC_STRING = 3;
  201. my $BINRPC_DOUBLE = 4;
  202. my $BINRPC_BASE64 = 17;
  203. my $BINRPC_ARRAY = 256;
  204. my $BINRPC_STRUCT = 257;
  205. # Declare functions
  206. # FHEM standard functions
  207. sub HMCCU_Initialize ($);
  208. sub HMCCU_Define ($$);
  209. sub HMCCU_InitDevice ($);
  210. sub HMCCU_Undef ($$);
  211. sub HMCCU_Shutdown ($);
  212. sub HMCCU_Set ($@);
  213. sub HMCCU_Get ($@);
  214. sub HMCCU_Attr ($@);
  215. sub HMCCU_AttrInterfacesPorts ($$$);
  216. sub HMCCU_Notify ($$);
  217. sub HMCCU_Detail ($$$$);
  218. # Aggregation
  219. sub HMCCU_AggregateReadings ($$);
  220. sub HMCCU_AggregationRules ($$);
  221. # Handling of default attributes
  222. sub HMCCU_ExportDefaults ($$);
  223. sub HMCCU_ExportDefaultsCSV ($$);
  224. sub HMCCU_ImportDefaults ($);
  225. sub HMCCU_FindDefaults ($$);
  226. sub HMCCU_SetDefaults ($);
  227. sub HMCCU_GetDefaults ($$);
  228. # Status and logging functions
  229. sub HMCCU_Trace ($$$$);
  230. sub HMCCU_Log ($$$$);
  231. sub HMCCU_LogError ($$$);
  232. sub HMCCU_SetError ($@);
  233. sub HMCCU_SetState ($@);
  234. sub HMCCU_SetRPCState ($@);
  235. # Filter and modify readings
  236. sub HMCCU_FilterReading ($$$);
  237. sub HMCCU_FormatReadingValue ($$$);
  238. sub HMCCU_GetReadingName ($$$$$$$);
  239. sub HMCCU_ScaleValue ($$$$$);
  240. sub HMCCU_Substitute ($$$$$);
  241. sub HMCCU_SubstRule ($$$);
  242. sub HMCCU_SubstVariables ($$$);
  243. # Update client device readings
  244. sub HMCCU_BulkUpdate ($$$$);
  245. sub HMCCU_GetUpdate ($$$);
  246. sub HMCCU_UpdateClients ($$$$$);
  247. sub HMCCU_UpdateInternalValues ($$$$);
  248. sub HMCCU_UpdateMultipleDevices ($$);
  249. sub HMCCU_UpdatePeers ($$$$);
  250. sub HMCCU_UpdateSingleDatapoint ($$$$);
  251. sub HMCCU_UpdateSingleDevice ($$$$);
  252. # RPC functions
  253. sub HMCCU_GetRPCCallbackURL ($$$$$);
  254. sub HMCCU_GetRPCDevice ($$$);
  255. sub HMCCU_GetRPCInterfaceList ($);
  256. sub HMCCU_GetRPCPortList ($);
  257. sub HMCCU_GetRPCServerInfo ($$$);
  258. sub HMCCU_IsRPCServerRunning ($$$);
  259. sub HMCCU_IsRPCType ($$$);
  260. sub HMCCU_IsRPCStateBlocking ($);
  261. sub HMCCU_ResetCounters ($);
  262. sub HMCCU_RPCDeRegisterCallback ($);
  263. sub HMCCU_RPCRegisterCallback ($);
  264. sub HMCCU_RPCGetConfig ($$$$);
  265. sub HMCCU_RPCSetConfig ($$$);
  266. sub HMCCU_StartExtRPCServer ($);
  267. sub HMCCU_StartIntRPCServer ($);
  268. sub HMCCU_StopExtRPCServer ($);
  269. sub HMCCU_StopRPCServer ($);
  270. # Parse and validate names and addresses
  271. sub HMCCU_ParseObject ($$$);
  272. sub HMCCU_IsDevAddr ($$);
  273. sub HMCCU_IsChnAddr ($$);
  274. sub HMCCU_SplitChnAddr ($);
  275. # FHEM device handling functions
  276. sub HMCCU_AssignIODevice ($$$);
  277. sub HMCCU_FindClientDevices ($$$$);
  278. sub HMCCU_FindIODevice ($);
  279. sub HMCCU_GetHash ($@);
  280. sub HMCCU_GetAttribute ($$$$);
  281. sub HMCCU_GetFlags ($);
  282. sub HMCCU_GetAttrReadingFormat ($$);
  283. sub HMCCU_GetAttrSubstitute ($$);
  284. sub HMCCU_IODeviceStates ();
  285. sub HMCCU_IsFlag ($$);
  286. # Handle interfaces, devices and channels
  287. sub HMCCU_CreateDevice ($$$$$);
  288. sub HMCCU_DeleteDevice ($);
  289. sub HMCCU_FormatDeviceInfo ($);
  290. sub HMCCU_GetAddress ($$$$);
  291. sub HMCCU_GetAffectedAddresses ($);
  292. sub HMCCU_GetCCUDeviceParam ($$);
  293. sub HMCCU_GetChannelName ($$$);
  294. sub HMCCU_GetDeviceChannels ($$$);
  295. sub HMCCU_GetDeviceInfo ($$$);
  296. sub HMCCU_GetDeviceInterface ($$$);
  297. sub HMCCU_GetDeviceList ($);
  298. sub HMCCU_GetDeviceName ($$$);
  299. sub HMCCU_GetDeviceType ($$$);
  300. sub HMCCU_GetFirmwareVersions ($$);
  301. sub HMCCU_GetGroupMembers ($$);
  302. sub HMCCU_GetMatchingDevices ($$$$);
  303. sub HMCCU_IsValidChannel ($$$);
  304. sub HMCCU_IsValidDevice ($$$);
  305. sub HMCCU_IsValidDeviceOrChannel ($$$);
  306. sub HMCCU_UpdateDeviceTable ($$);
  307. # Handle datapoints
  308. sub HMCCU_FindDatapoint ($$$$$);
  309. sub HMCCU_GetChannel ($$);
  310. sub HMCCU_GetDatapoint ($@);
  311. sub HMCCU_GetDatapointAttr ($$$$$);
  312. sub HMCCU_GetDatapointCount ($$$);
  313. sub HMCCU_GetDatapointList ($$$);
  314. sub HMCCU_GetSpecialDatapoints ($$$$$);
  315. sub HMCCU_GetSwitchDatapoint ($$$);
  316. sub HMCCU_GetValidDatapoints ($$$$$);
  317. sub HMCCU_IsValidDatapoint ($$$$$);
  318. sub HMCCU_SetDatapoint ($$$);
  319. sub HMCCU_SetMultipleDatapoints ($$);
  320. # Internal RPC server functions
  321. sub HMCCU_ResetRPCQueue ($$);
  322. sub HMCCU_ReadRPCQueue ($);
  323. sub HMCCU_ProcessEvent ($$);
  324. # Homematic script and variable functions
  325. sub HMCCU_GetVariables ($$);
  326. sub HMCCU_HMCommand ($$$);
  327. sub HMCCU_HMCommandCB ($$$);
  328. sub HMCCU_HMCommandNB ($$$);
  329. sub HMCCU_HMScriptExt ($$$);
  330. sub HMCCU_SetVariable ($$$$$);
  331. sub HMCCU_UpdateVariables ($);
  332. # File queue functions
  333. sub HMCCU_QueueOpen ($$);
  334. sub HMCCU_QueueClose ($);
  335. sub HMCCU_QueueReset ($);
  336. sub HMCCU_QueueEnq ($$);
  337. sub HMCCU_QueueDeq ($);
  338. # Helper functions
  339. sub HMCCU_CalculateReading ($$);
  340. sub HMCCU_EncodeEPDisplay ($);
  341. sub HMCCU_ExprMatch ($$$);
  342. sub HMCCU_ExprNotMatch ($$$);
  343. sub HMCCU_GetDutyCycle ($);
  344. sub HMCCU_GetHMState ($$$);
  345. sub HMCCU_GetTimeSpec ($);
  346. sub HMCCU_CorrectName ($);
  347. sub HMCCU_RefToString ($);
  348. sub HMCCU_ResolveName ($$);
  349. sub HMCCU_TCPConnect ($$);
  350. sub HMCCU_TCPPing ($$$);
  351. # Subprocess functions of internal RPC server
  352. sub HMCCU_CCURPC_Write ($$);
  353. sub HMCCU_CCURPC_OnRun ($);
  354. sub HMCCU_CCURPC_OnExit ();
  355. sub HMCCU_CCURPC_NewDevicesCB ($$$);
  356. sub HMCCU_CCURPC_DeleteDevicesCB ($$$);
  357. sub HMCCU_CCURPC_UpdateDeviceCB ($$$$);
  358. sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$);
  359. sub HMCCU_CCURPC_ReaddDevicesCB ($$$);
  360. sub HMCCU_CCURPC_EventCB ($$$$$);
  361. sub HMCCU_CCURPC_ListDevicesCB ($$);
  362. ##################################################
  363. # Initialize module
  364. ##################################################
  365. sub HMCCU_Initialize ($)
  366. {
  367. my ($hash) = @_;
  368. $hash->{DefFn} = "HMCCU_Define";
  369. $hash->{UndefFn} = "HMCCU_Undef";
  370. $hash->{SetFn} = "HMCCU_Set";
  371. $hash->{GetFn} = "HMCCU_Get";
  372. $hash->{ReadFn} = "HMCCU_Read";
  373. $hash->{AttrFn} = "HMCCU_Attr";
  374. $hash->{NotifyFn} = "HMCCU_Notify";
  375. $hash->{ShutdownFn} = "HMCCU_Shutdown";
  376. $hash->{FW_detailFn} = "HMCCU_Detail";
  377. $hash->{parseParams} = 1;
  378. $hash->{AttrList} = "stripchar stripnumber ccuaggregate:textField-long".
  379. " ccudefaults rpcinterfaces:multiple-strict,".join(',',sort keys %HMCCU_RPC_PORT).
  380. " ccudef-hmstatevals:textField-long ccudef-substitute:textField-long".
  381. " ccudef-readingname:textField-long ccudef-readingfilter:textField-long".
  382. " ccudef-readingformat:name,namelc,address,addresslc,datapoint,datapointlc".
  383. " ccuflags:multiple-strict,extrpc,intrpc,procrpc,dptnocheck,noagg,nohmstate,logEvents,noEvents,noReadings,nonBlocking".
  384. " ccuReqTimeout ccuGetVars rpcinterval:2,3,5,7,10 rpcqueue".
  385. " rpcport:multiple-strict,".join(',',sort keys %HMCCU_RPC_NUMPORT).
  386. " rpcserver:on,off rpcserveraddr rpcserverport rpctimeout rpcevtimeout parfile substitute".
  387. " ccuget:Value,State ".
  388. $readingFnAttributes;
  389. }
  390. ######################################################################
  391. # Define device
  392. ######################################################################
  393. sub HMCCU_Define ($$)
  394. {
  395. my ($hash, $a, $h) = @_;
  396. my $name = $hash->{NAME};
  397. return "Specify CCU hostname or IP address as a parameter" if (scalar (@$a) < 3);
  398. $hash->{host} = $$a[2];
  399. $hash->{Clients} = ':HMCCUDEV:HMCCUCHN:HMCCURPC:HMCCURPCPROC:';
  400. $hash->{hmccu}{ccu}{delay} = exists ($h->{ccudelay}) ? $h->{ccudelay} : $HMCCU_CCU_BOOT_DELAY;
  401. $hash->{hmccu}{ccu}{timeout} = exists ($h->{waitforccu}) ? $h->{waitforccu} : $HMCCU_CCU_PING_TIMEOUT;
  402. $hash->{hmccu}{ccu}{delayed} = 0;
  403. # Check if TCL-Rega process is running on CCU (CCU reachable)
  404. if (exists ($h->{delayedinit}) && $h->{delayedinit} > 0) {
  405. return "Value for delayed initialization must be greater than $HMCCU_CCU_DELAYED_INIT"
  406. if ($h->{delayedinit} <= $HMCCU_CCU_DELAYED_INIT);
  407. $hash->{hmccu}{ccu}{delay} = $h->{delayedinit};
  408. $hash->{ccustate} = 'unreachable';
  409. HMCCU_Log ($hash, 1, "Forced delayed initialization", 0);
  410. }
  411. else {
  412. if (HMCCU_TCPPing ($hash->{host}, 8181, $hash->{hmccu}{ccu}{timeout})) {
  413. $hash->{ccustate} = 'active';
  414. }
  415. else {
  416. $hash->{ccustate} = 'unreachable';
  417. HMCCU_Log ($hash, 1, "CCU port 8181 is not reachable", 0);
  418. }
  419. }
  420. # Get CCU IP address
  421. $hash->{ccuip} = HMCCU_ResolveName ($hash->{host}, 'N/A');
  422. # Get CCU number (if more than one)
  423. if (scalar (@$a) >= 4) {
  424. return "CCU number must be in range 1-9" if ($$a[3] < 1 || $$a[3] > 9);
  425. $hash->{CCUNum} = $$a[3];
  426. }
  427. else {
  428. # Count CCU devices
  429. my $ccucount = 0;
  430. foreach my $d (keys %defs) {
  431. my $ch = $defs{$d};
  432. next if (!exists ($ch->{TYPE}));
  433. $ccucount++ if ($ch->{TYPE} eq 'HMCCU' && $ch != $hash);
  434. }
  435. $hash->{CCUNum} = $ccucount+1;
  436. }
  437. $hash->{version} = $HMCCU_VERSION;
  438. $hash->{ccutype} = 'CCU2/3';
  439. $hash->{RPCState} = "inactive";
  440. $hash->{NOTIFYDEV} = "global,TYPE=(HMCCU|HMCCUDEV|HMCCUCHN)";
  441. HMCCU_Log ($hash, 1, "Initialized version $HMCCU_VERSION", 0);
  442. my $rc = 0;
  443. if ($hash->{ccustate} eq 'active') {
  444. # If CCU is alive read devices, channels, interfaces and groups
  445. HMCCU_Log ($hash, 1, "HMCCU: Initializing device", 0);
  446. $rc = HMCCU_InitDevice ($hash);
  447. }
  448. if ($hash->{ccustate} ne 'active' || $rc > 0) {
  449. # Schedule update of CCU assets if CCU is not active during FHEM startup
  450. if (!$init_done) {
  451. $hash->{hmccu}{ccu}{delayed} = 1;
  452. HMCCU_Log ($hash, 1, "Scheduling delayed initialization in ".$hash->{hmccu}{ccu}{delay}." seconds", 0);
  453. InternalTimer (gettimeofday()+$hash->{hmccu}{ccu}{delay}, "HMCCU_InitDevice", $hash);
  454. }
  455. }
  456. $hash->{hmccu}{evtime} = 0;
  457. $hash->{hmccu}{evtimeout} = 0;
  458. $hash->{hmccu}{updatetime} = 0;
  459. $hash->{hmccu}{rpccount} = 0;
  460. $hash->{hmccu}{rpcports} = $HMCCU_RPC_PORT_DEFAULT;
  461. readingsBeginUpdate ($hash);
  462. readingsBulkUpdate ($hash, "state", "Initialized");
  463. readingsBulkUpdate ($hash, "rpcstate", "inactive");
  464. readingsEndUpdate ($hash, 1);
  465. $attr{$name}{stateFormat} = "rpcstate/state";
  466. return undef;
  467. }
  468. ######################################################################
  469. # Initialization of FHEM device.
  470. # Called during Define() or by HMCCU after CCU ready.
  471. # Return 0 on successful initialization or >0 on error:
  472. # 1 = CCU port 8181 is not reachable.
  473. # 2 = Error while reading device list from CCU.
  474. ######################################################################
  475. sub HMCCU_InitDevice ($)
  476. {
  477. my ($hash) = @_;
  478. my $name = $hash->{NAME};
  479. if ($hash->{hmccu}{ccu}{delayed} == 1) {
  480. HMCCU_Log ($hash, 1, "HMCCU: Initializing devices", 0);
  481. if (!HMCCU_TCPPing ($hash->{host}, 8181, $hash->{hmccu}{ccu}{timeout})) {
  482. $hash->{ccustate} = 'unreachable';
  483. HMCCU_Log ($hash, 1, "HMCCU: CCU port 8181 is not reachable", 0);
  484. return 1;
  485. }
  486. }
  487. my ($devcnt, $chncnt, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash);
  488. if ($devcnt >= 0) {
  489. HMCCU_Log ($hash, 1, "HMCCU: Read $devcnt devices with $chncnt channels from CCU ".$hash->{host}, 0);
  490. HMCCU_Log ($hash, 1, "HMCCU: Read $ifcount interfaces from CCU ".$hash->{host}, 0);
  491. HMCCU_Log ($hash, 1, "HMCCU: Read $prgcount programs from CCU ".$hash->{host}, 0);
  492. HMCCU_Log ($hash, 1, "HMCCU: Read $gcount virtual groups from CCU ".$hash->{host}, 0);
  493. return 0;
  494. }
  495. else {
  496. HMCCU_Log ($hash, 1, "HMCCU: Error while reading device list from CCU ".$hash->{host}, 0);
  497. return 2;
  498. }
  499. }
  500. ######################################################################
  501. # Set or delete attribute
  502. ######################################################################
  503. sub HMCCU_Attr ($@)
  504. {
  505. my ($cmd, $name, $attrname, $attrval) = @_;
  506. my $hash = $defs{$name};
  507. my $rc = 0;
  508. if ($cmd eq 'set') {
  509. if ($attrname eq 'ccudefaults') {
  510. $rc = HMCCU_ImportDefaults ($attrval);
  511. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  512. if ($rc < 0) {
  513. $rc = -$rc;
  514. return HMCCU_SetError ($hash,
  515. "Syntax error in default attribute file $attrval line $rc");
  516. }
  517. }
  518. elsif ($attrname eq 'ccuaggregate') {
  519. $rc = HMCCU_AggregationRules ($hash, $attrval);
  520. return HMCCU_SetError ($hash, "Syntax error in attribute ccuaggregate") if ($rc == 0);
  521. }
  522. elsif ($attrname eq 'ccuackstate') {
  523. return "HMCCU: Attribute ccuackstate is depricated. Use ccuflags with 'ackState' instead";
  524. }
  525. elsif ($attrname eq 'ccureadings') {
  526. return "HMCCU: Attribute ccureadings is depricated. Use ccuflags with 'noReadings' instead";
  527. }
  528. elsif ($attrname eq 'ccuflags') {
  529. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  530. my @flags = ($attrval =~ /(intrpc|extrpc|procrpc)/g);
  531. return "Flags extrpc, procrpc and intrpc cannot be combined" if (scalar (@flags) > 1);
  532. if ($attrval =~ /(extrpc|intrpc|procrpc)/) {
  533. my $rpcmode = $1;
  534. if ($ccuflags !~ /$rpcmode/) {
  535. return "Stop RPC server before switching RPC server"
  536. if (HMCCU_IsRPCServerRunning ($hash, undef, undef));
  537. }
  538. }
  539. if ($attrval =~ /extrpc/) {
  540. HMCCU_Log ($hash, 1, "RPC server mode extrpc will be ignored. Using procrpc instead", 0);
  541. }
  542. elsif ($attrval =~ /intrpc/) {
  543. HMCCU_Log ($hash, 1, "RPC server mode intrpc is deprecated. Please use procrpc instead", 0);
  544. }
  545. }
  546. elsif ($attrname eq 'ccuGetVars') {
  547. my ($interval, $pattern) = split /:/, $attrval;
  548. $pattern = '.*' if (!defined ($pattern));
  549. $hash->{hmccu}{ccuvarspat} = $pattern;
  550. $hash->{hmccu}{ccuvarsint} = $interval;
  551. RemoveInternalTimer ($hash, "HMCCU_UpdateVariables");
  552. if ($interval > 0) {
  553. HMCCU_Log ($hash, 2, "Updating CCU system variables every $interval seconds", 0);
  554. InternalTimer (gettimeofday()+$interval, "HMCCU_UpdateVariables", $hash);
  555. }
  556. }
  557. elsif ($attrname eq 'rpcdevice') {
  558. return "HMCCU: Attribute rpcdevice is depricated. Please remove it";
  559. # return "HMCCU: Can't find HMCCURPC device $attrval"
  560. # if (!exists ($defs{$attrval}) || $defs{$attrval}->{TYPE} ne 'HMCCURPC');
  561. # if (exists ($defs{$attrval}->{IODev})) {
  562. # return "HMCCU: Device $attrval is not assigned to $name"
  563. # if ($defs{$attrval}->{IODev} != $hash);
  564. # }
  565. # else {
  566. # $defs{$attrval}->{IODev} = $hash;
  567. # }
  568. # $hash->{RPCDEV} = $attrval;
  569. }
  570. elsif ($attrname eq 'rpcinterfaces' || $attrname eq 'rpcport') {
  571. if ($hash->{hmccu}{ccu}{delayed} == 0) {
  572. my $msg = HMCCU_AttrInterfacesPorts ($hash, $attrname, $attrval);
  573. return $msg if ($msg ne '');
  574. }
  575. # my @ports = split (',', $attrval);
  576. # my @plist = ();
  577. # foreach my $p (@ports) {
  578. # my $pn = HMCCU_GetRPCServerInfo ($hash, $p, 'port');
  579. # return "HMCCU: Illegal RPC interface $p" if (!defined ($pn));
  580. # push (@plist, $pn);
  581. # }
  582. # return "No RPC interface specified" if (scalar (@plist) == 0);
  583. # $hash->{hmccu}{rpcports} = join (',', @plist);
  584. # $attr{$name}{"rpcport"} = $hash->{hmccu}{rpcports};
  585. }
  586. # elsif ($attrname eq 'rpcport') {
  587. # my @ports = split (',', $attrval);
  588. # my @ilist = ();
  589. # foreach my $p (@ports) {
  590. # my $pn = HMCCU_GetRPCServerInfo ($hash, $p, 'name');
  591. # return "HMCCU: Illegal RPC port $p" if (!defined ($pn));
  592. # push (@ilist, $pn);
  593. # }
  594. # return "No RPC port specified" if (scalar (@ilist) == 0);
  595. # $hash->{hmccu}{rpcports} = $attrval;
  596. # $attr{$name}{"rpcinterfaces"} = join (',', @ilist);
  597. # }
  598. }
  599. elsif ($cmd eq 'del') {
  600. if ($attrname eq 'ccuaggregate') {
  601. HMCCU_AggregationRules ($hash, '');
  602. }
  603. elsif ($attrname eq 'ccuGetVars') {
  604. RemoveInternalTimer ($hash, "HMCCU_UpdateVariables");
  605. }
  606. elsif ($attrname eq 'rpcdevice') {
  607. delete $hash->{RPCDEV} if (exists ($hash->{RPCDEV}));
  608. }
  609. elsif ($attrname eq 'rpcport' || $attrname eq 'rpcinterfaces') {
  610. $hash->{hmccu}{rpcports} = $HMCCU_RPC_PORT_DEFAULT;
  611. }
  612. }
  613. return undef;
  614. }
  615. ######################################################################
  616. # Set attributes rpcinterfaces and rpcport.
  617. # Return empty string or error message on error.
  618. ######################################################################
  619. sub HMCCU_AttrInterfacesPorts ($$$) {
  620. my ($hash, $attr, $attrval) = @_;
  621. my $name = $hash->{NAME};
  622. if ($attr eq 'rpcinterfaces') {
  623. my @ports = split (',', $attrval);
  624. my @plist = ();
  625. foreach my $p (@ports) {
  626. my $pn = HMCCU_GetRPCServerInfo ($hash, $p, 'port');
  627. return "HMCCU: Illegal RPC interface $p" if (!defined ($pn));
  628. push (@plist, $pn);
  629. }
  630. return "No RPC interface specified" if (scalar (@plist) == 0);
  631. $hash->{hmccu}{rpcports} = join (',', @plist);
  632. $attr{$name}{"rpcport"} = $hash->{hmccu}{rpcports};
  633. }
  634. elsif ($attr eq 'rpcport') {
  635. my @ports = split (',', $attrval);
  636. my @ilist = ();
  637. foreach my $p (@ports) {
  638. my $pn = HMCCU_GetRPCServerInfo ($hash, $p, 'name');
  639. return "HMCCU: Illegal RPC port $p" if (!defined ($pn));
  640. push (@ilist, $pn);
  641. }
  642. return "No RPC port specified" if (scalar (@ilist) == 0);
  643. $hash->{hmccu}{rpcports} = $attrval;
  644. $attr{$name}{"rpcinterfaces"} = join (',', @ilist);
  645. }
  646. return '';
  647. }
  648. ######################################################################
  649. # Parse aggregation rules for readings.
  650. # Syntax of aggregation rule is:
  651. # FilterSpec[;...]
  652. # FilterSpec := {Name|Filt|Read|Cond|Else|Pref|Coll|Html}[,...]
  653. # Name := name:Name
  654. # Filt := filter:{name|type|group|room|alias}=Regexp[!Regexp]
  655. # Read := read:Regexp
  656. # Cond := if:{any|all|min|max|sum|avg|gt|lt|ge|le}=Value
  657. # Else := else:Value
  658. # Pref := prefix:{RULE|Prefix}
  659. # Coll := coll:{NAME|Attribute}
  660. # Html := html:Template
  661. ######################################################################
  662. sub HMCCU_AggregationRules ($$)
  663. {
  664. my ($hash, $rulestr) = @_;
  665. my $name = $hash->{NAME};
  666. # Delete existing aggregation rules
  667. if (exists ($hash->{hmccu}{agg})) {
  668. delete $hash->{hmccu}{agg};
  669. }
  670. return if ($rulestr eq '');
  671. my @pars = ('name', 'filter', 'if', 'else');
  672. # Extract aggregation rules
  673. my $cnt = 0;
  674. my @rules = split (/[;\n]+/, $rulestr);
  675. foreach my $r (@rules) {
  676. $cnt++;
  677. # Set default rule parameters. Can be modified later
  678. my %opt = ( 'read' => 'state', 'prefix' => 'RULE', 'coll' => 'NAME' );
  679. # Parse aggregation rule
  680. my @specs = split (',', $r);
  681. foreach my $spec (@specs) {
  682. if ($spec =~ /^(name|filter|read|if|else|prefix|coll|html):(.+)$/) {
  683. $opt{$1} = $2;
  684. }
  685. }
  686. # Check if mandatory parameters are specified
  687. foreach my $p (@pars) {
  688. return HMCCU_Log ($hash, 1, "Parameter $p is missing in aggregation rule $cnt.", 0)
  689. if (!exists ($opt{$p}));
  690. }
  691. my $fname = $opt{name};
  692. my ($fincl, $fexcl) = split ('!', $opt{filter});
  693. my ($ftype, $fexpr) = split ('=', $fincl);
  694. return 0 if (!defined ($fexpr));
  695. my ($fcond, $fval) = split ('=', $opt{if});
  696. return 0 if (!defined ($fval));
  697. my ($fcoll, $fdflt) = split ('!', $opt{coll});
  698. $fdflt = 'no match' if (!defined ($fdflt));
  699. my $fhtml = exists ($opt{'html'}) ? $opt{'html'} : '';
  700. # Read HTML template (optional)
  701. if ($fhtml ne '') {
  702. my %tdef;
  703. my @html;
  704. # Read template file
  705. if (open (TEMPLATE, "<$fhtml")) {
  706. @html = <TEMPLATE>;
  707. close (TEMPLATE);
  708. }
  709. else {
  710. return HMCCU_Log ($hash, 1, "Can't open file $fhtml.", 0);
  711. }
  712. # Parse template
  713. foreach my $line (@html) {
  714. chomp $line;
  715. my ($key, $h) = split /:/, $line, 2;
  716. next if (!defined ($h) || $key =~ /^#/);
  717. $tdef{$key} = $h;
  718. }
  719. # Some syntax checks
  720. return HMCCU_Log ($hash, 1, "Missing definition row-odd in template file.", 0)
  721. if (!exists ($tdef{'row-odd'}));
  722. # Set default values
  723. $tdef{'begin-html'} = '' if (!exists ($tdef{'begin-html'}));
  724. $tdef{'end-html'} = '' if (!exists ($tdef{'end-html'}));
  725. $tdef{'begin-table'} = "<table>" if (!exists ($tdef{'begin-table'}));
  726. $tdef{'end-table'} = "</table>" if (!exists ($tdef{'end-table'}));
  727. $tdef{'default'} = 'no data' if (!exists ($tdef{'default'}));;
  728. $tdef{'row-even'} = $tdef{'row-odd'} if (!exists ($tdef{'row-even'}));
  729. foreach my $t (keys %tdef) {
  730. $hash->{hmccu}{agg}{$fname}{fhtml}{$t} = $tdef{$t};
  731. }
  732. }
  733. $hash->{hmccu}{agg}{$fname}{ftype} = $ftype;
  734. $hash->{hmccu}{agg}{$fname}{fexpr} = $fexpr;
  735. $hash->{hmccu}{agg}{$fname}{fexcl} = (defined ($fexcl) ? $fexcl : '');
  736. $hash->{hmccu}{agg}{$fname}{fread} = $opt{'read'};
  737. $hash->{hmccu}{agg}{$fname}{fcond} = $fcond;
  738. $hash->{hmccu}{agg}{$fname}{ftrue} = $fval;
  739. $hash->{hmccu}{agg}{$fname}{felse} = $opt{'else'};
  740. $hash->{hmccu}{agg}{$fname}{fpref} = $opt{prefix} eq 'RULE' ? $fname : $opt{prefix};
  741. $hash->{hmccu}{agg}{$fname}{fcoll} = $fcoll;
  742. $hash->{hmccu}{agg}{$fname}{fdflt} = $fdflt;
  743. }
  744. return 1;
  745. }
  746. ######################################################################
  747. # Export default attributes.
  748. ######################################################################
  749. sub HMCCU_ExportDefaults ($$)
  750. {
  751. my ($filename, $all) = @_;
  752. return 0 if (!open (DEFFILE, ">$filename"));
  753. print DEFFILE "# HMCCU default attributes for channels\n";
  754. foreach my $t (keys %{$HMCCU_CHN_DEFAULTS}) {
  755. print DEFFILE "\nchannel:$t\n";
  756. foreach my $a (sort keys %{$HMCCU_CHN_DEFAULTS->{$t}}) {
  757. print DEFFILE "$a=".$HMCCU_CHN_DEFAULTS->{$t}{$a}."\n";
  758. }
  759. }
  760. print DEFFILE "\n# HMCCU default attributes for devices\n";
  761. foreach my $t (keys %{$HMCCU_DEV_DEFAULTS}) {
  762. print DEFFILE "\ndevice:$t\n";
  763. foreach my $a (sort keys %{$HMCCU_DEV_DEFAULTS->{$t}}) {
  764. print DEFFILE "$a=".$HMCCU_DEV_DEFAULTS->{$t}{$a}."\n";
  765. }
  766. }
  767. if ($all) {
  768. print DEFFILE "# HMCCU custom default attributes for channels\n";
  769. foreach my $t (keys %HMCCU_CUST_CHN_DEFAULTS) {
  770. print DEFFILE "\nchannel:$t\n";
  771. foreach my $a (sort keys %{$HMCCU_CUST_CHN_DEFAULTS{$t}}) {
  772. print DEFFILE "$a=".$HMCCU_CUST_CHN_DEFAULTS{$t}{$a}."\n";
  773. }
  774. }
  775. print DEFFILE "\n# HMCCU custom default attributes for devices\n";
  776. foreach my $t (keys %HMCCU_CUST_DEV_DEFAULTS) {
  777. print DEFFILE "\ndevice:$t\n";
  778. foreach my $a (sort keys %{$HMCCU_CUST_DEV_DEFAULTS{$t}}) {
  779. print DEFFILE "$a=".$HMCCU_CUST_DEV_DEFAULTS{$t}{$a}."\n";
  780. }
  781. }
  782. }
  783. close (DEFFILE);
  784. return 1;
  785. }
  786. ######################################################################
  787. # Export default attributes as CSV file.
  788. ######################################################################
  789. sub HMCCU_ExportDefaultsCSV ($$)
  790. {
  791. my ($filename, $all) = @_;
  792. my %attrlist = (
  793. '_type' => '', '_description' => '', '_channels' => '',
  794. 'ccureadingfilter' => '', 'ccureadingname' => '', 'ccuscaleval' => '', 'cmdIcon' => '', 'controldatapoint' => '',
  795. 'eventMap' => '', 'event-on-change-reading' => '', 'event-on-update-reading' => '',
  796. 'genericDeviceType' => '',
  797. 'hmstatevals' => '',
  798. 'statedatapoint' => '', 'statevals' => '', 'stripnumber' => '', 'substexcl' => '', 'substitute' => '',
  799. 'webCmd' => '', 'widgetOverride' => ''
  800. );
  801. return 0 if (!open (DEFFILE, ">$filename"));
  802. # Write header
  803. print DEFFILE "_flag,".join (',', sort keys %attrlist)."\n";
  804. # Write channel configurations
  805. foreach my $t (keys %{$HMCCU_CHN_DEFAULTS}) {
  806. print DEFFILE "C";
  807. $attrlist{'_type'} = $t;
  808. foreach $a (sort keys %attrlist) {
  809. my $v = exists ($HMCCU_CHN_DEFAULTS->{$t}{$a}) ? $HMCCU_CHN_DEFAULTS->{$t}{$a} : $attrlist{$a};
  810. print DEFFILE ",\"$v\"";
  811. }
  812. print DEFFILE "\n";
  813. }
  814. # Write device configurations
  815. foreach my $t (keys %{$HMCCU_DEV_DEFAULTS}) {
  816. print DEFFILE "D";
  817. $attrlist{'_type'} = $t;
  818. foreach $a (sort keys %attrlist) {
  819. my $v = exists ($HMCCU_DEV_DEFAULTS->{$t}{$a}) ? $HMCCU_DEV_DEFAULTS->{$t}{$a} : $attrlist{$a};
  820. print DEFFILE ",\"$v\"";
  821. }
  822. print DEFFILE "\n";
  823. }
  824. if ($all) {
  825. # Write channel configurations
  826. foreach my $t (keys %HMCCU_CUST_CHN_DEFAULTS) {
  827. print DEFFILE "C";
  828. $attrlist{'_type'} = $t;
  829. foreach $a (sort keys %attrlist) {
  830. my $v = exists ($HMCCU_CUST_CHN_DEFAULTS{$t}{$a}) ? $HMCCU_CUST_CHN_DEFAULTS{$t}{$a} : $attrlist{$a};
  831. print DEFFILE ",\"$v\"";
  832. }
  833. print DEFFILE "\n";
  834. }
  835. # Write device configurations
  836. foreach my $t (keys %HMCCU_CUST_DEV_DEFAULTS) {
  837. print DEFFILE "D";
  838. $attrlist{'_type'} = $t;
  839. foreach $a (sort keys %attrlist) {
  840. my $v = exists ($HMCCU_CUST_DEV_DEFAULTS{$t}{$a}) ? $HMCCU_CUST_DEV_DEFAULTS{$t}{$a} : $attrlist{$a};
  841. print DEFFILE ",\"$v\"";
  842. }
  843. print DEFFILE "\n";
  844. }
  845. }
  846. close (DEFFILE);
  847. return 1;
  848. }
  849. ######################################################################
  850. # Import customer default attributes
  851. # Returns 1 on success. Returns negative line number on syntax errors.
  852. # Returns 0 on file open error.
  853. ######################################################################
  854. sub HMCCU_ImportDefaults ($)
  855. {
  856. my ($filename) = @_;
  857. my $modtype = '';
  858. my $ccutype = '';
  859. my $line = 0;
  860. return 0 if (!open (DEFFILE, "<$filename"));
  861. my @defaults = <DEFFILE>;
  862. close (DEFFILE);
  863. chomp (@defaults);
  864. %HMCCU_CUST_CHN_DEFAULTS = ();
  865. %HMCCU_CUST_DEV_DEFAULTS = ();
  866. foreach my $d (@defaults) {
  867. $line++;
  868. next if ($d eq '' || $d =~ /^#/);
  869. if ($d =~ /^(channel|device):/) {
  870. my @t = split (':', $d, 2);
  871. if (scalar (@t) != 2) {
  872. close (DEFFILE);
  873. return -$line;
  874. }
  875. $modtype = $t[0];
  876. $ccutype = $t[1];
  877. next;
  878. }
  879. if ($ccutype eq '' || $modtype eq '') {
  880. close (DEFFILE);
  881. return -$line;
  882. }
  883. my @av = split ('=', $d, 2);
  884. if (scalar (@av) != 2) {
  885. close (DEFFILE);
  886. return -$line;
  887. }
  888. if ($modtype eq 'channel') {
  889. $HMCCU_CUST_CHN_DEFAULTS{$ccutype}{$av[0]} = $av[1];
  890. }
  891. else {
  892. $HMCCU_CUST_DEV_DEFAULTS{$ccutype}{$av[0]} = $av[1];
  893. }
  894. }
  895. return 1;
  896. }
  897. ######################################################################
  898. # Find default attributes
  899. # Return template reference.
  900. ######################################################################
  901. sub HMCCU_FindDefaults ($$)
  902. {
  903. my ($hash, $common) = @_;
  904. my $type = $hash->{TYPE};
  905. my $ccutype = $hash->{ccutype};
  906. if ($type eq 'HMCCUCHN') {
  907. my ($adr, $chn) = split (':', $hash->{ccuaddr});
  908. foreach my $deftype (keys %HMCCU_CUST_CHN_DEFAULTS) {
  909. my @chnlst = split (',', $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_channels});
  910. return \%{$HMCCU_CUST_CHN_DEFAULTS{$deftype}}
  911. if ($ccutype =~ /^($deftype)$/i && grep { $_ eq $chn} @chnlst);
  912. }
  913. foreach my $deftype (keys %{$HMCCU_CHN_DEFAULTS}) {
  914. my @chnlst = split (',', $HMCCU_CHN_DEFAULTS->{$deftype}{_channels});
  915. return \%{$HMCCU_CHN_DEFAULTS->{$deftype}}
  916. if ($ccutype =~ /^($deftype)$/i && grep { $_ eq $chn} @chnlst);
  917. }
  918. }
  919. elsif ($type eq 'HMCCUDEV' || $type eq 'HMCCU') {
  920. foreach my $deftype (keys %HMCCU_CUST_DEV_DEFAULTS) {
  921. return \%{$HMCCU_CUST_DEV_DEFAULTS{$deftype}} if ($ccutype =~ /^($deftype)$/i);
  922. }
  923. foreach my $deftype (keys %{$HMCCU_DEV_DEFAULTS}) {
  924. return \%{$HMCCU_DEV_DEFAULTS->{$deftype}} if ($ccutype =~ /^($deftype)$/i);
  925. }
  926. }
  927. return undef;
  928. }
  929. ######################################################################
  930. # Set default attributes from template
  931. ######################################################################
  932. sub HMCCU_SetDefaultsTemplate ($$)
  933. {
  934. my ($hash, $template) = @_;
  935. my $name = $hash->{NAME};
  936. foreach my $a (keys %{$template}) {
  937. next if ($a =~ /^_/);
  938. my $v = $template->{$a};
  939. CommandAttr (undef, "$name $a $v");
  940. }
  941. }
  942. ######################################################################
  943. # Set default attributes
  944. ######################################################################
  945. sub HMCCU_SetDefaults ($)
  946. {
  947. my ($hash) = @_;
  948. my $name = $hash->{NAME};
  949. # Set type specific attributes
  950. my $template = HMCCU_FindDefaults ($hash, 0);
  951. return 0 if (!defined ($template));
  952. HMCCU_SetDefaultsTemplate ($hash, $template);
  953. return 1;
  954. }
  955. ######################################################################
  956. # List default attributes for device type (mode = 0) or all
  957. # device types (mode = 1) with default attributes available.
  958. ######################################################################
  959. sub HMCCU_GetDefaults ($$)
  960. {
  961. my ($hash, $mode) = @_;
  962. my $name = $hash->{NAME};
  963. my $type = $hash->{TYPE};
  964. my $ccutype = $hash->{ccutype};
  965. my $result = '';
  966. my $deffile = '';
  967. if ($mode == 0) {
  968. my $template = HMCCU_FindDefaults ($hash, 0);
  969. return ($result eq '' ? "No default attributes defined" : $result) if (!defined ($template));
  970. foreach my $a (keys %{$template}) {
  971. next if ($a =~ /^_/);
  972. my $v = $template->{$a};
  973. $result .= $a." = ".$v."\n";
  974. }
  975. }
  976. else {
  977. $result = "HMCCU Channels:\n------------------------------\n";
  978. foreach my $deftype (sort keys %{$HMCCU_CHN_DEFAULTS}) {
  979. my $tlist = $deftype;
  980. $tlist =~ s/\|/,/g;
  981. $result .= $HMCCU_CHN_DEFAULTS->{$deftype}{_description}." ($tlist), channels ".
  982. $HMCCU_CHN_DEFAULTS->{$deftype}{_channels}."\n";
  983. }
  984. $result .= "\nHMCCU Devices:\n------------------------------\n";
  985. foreach my $deftype (sort keys %{$HMCCU_DEV_DEFAULTS}) {
  986. my $tlist = $deftype;
  987. $tlist =~ s/\|/,/g;
  988. $result .= $HMCCU_DEV_DEFAULTS->{$deftype}{_description}." ($tlist)\n";
  989. }
  990. $result .= "\nCustom Channels:\n-----------------------------\n";
  991. foreach my $deftype (sort keys %HMCCU_CUST_CHN_DEFAULTS) {
  992. my $tlist = $deftype;
  993. $tlist =~ s/\|/,/g;
  994. $result .= $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_description}." ($tlist), channels ".
  995. $HMCCU_CUST_CHN_DEFAULTS{$deftype}{_channels}."\n";
  996. }
  997. $result .= "\nCustom Devices:\n-----------------------------\n";
  998. foreach my $deftype (sort keys %HMCCU_CUST_DEV_DEFAULTS) {
  999. my $tlist = $deftype;
  1000. $tlist =~ s/\|/,/g;
  1001. $result .= $HMCCU_CUST_DEV_DEFAULTS{$deftype}{_description}." ($tlist)\n";
  1002. }
  1003. }
  1004. return $result;
  1005. }
  1006. ######################################################################
  1007. # Handle FHEM events
  1008. ######################################################################
  1009. sub HMCCU_Notify ($$)
  1010. {
  1011. my ($hash, $devhash) = @_;
  1012. my $name = $hash->{NAME};
  1013. my $devname = $devhash->{NAME};
  1014. my $devtype = $devhash->{TYPE};
  1015. my $disable = AttrVal ($name, 'disable', 0);
  1016. my $rpcserver = AttrVal ($name, 'rpcserver', 'off');
  1017. my $ccuflags = HMCCU_GetFlags ($name);
  1018. return if ($disable);
  1019. my $events = deviceEvents ($devhash, 1);
  1020. return if (! $events);
  1021. # Process events
  1022. foreach my $event (@{$events}) {
  1023. if ($devname eq 'global') {
  1024. if ($event eq 'INITIALIZED') {
  1025. return if ($rpcserver eq 'off');
  1026. my $delay = $hash->{ccustate} eq 'active' && $hash->{hmccu}{ccu}{delayed} == 0 ?
  1027. $HMCCU_INIT_INTERVAL0 : $hash->{hmccu}{ccu}{delay}+$HMCCU_CCU_RPC_OFFSET;
  1028. Log3 $name, 0, "HMCCU: Start of RPC server after FHEM initialization in $delay seconds";
  1029. if ($ccuflags =~ /(extrpc|procrpc)/) {
  1030. InternalTimer (gettimeofday()+$delay, "HMCCU_StartExtRPCServer", $hash, 0);
  1031. }
  1032. else {
  1033. InternalTimer (gettimeofday()+$delay, "HMCCU_StartIntRPCServer", $hash, 0);
  1034. }
  1035. }
  1036. }
  1037. else {
  1038. return if ($devtype ne 'HMCCUDEV' && $devtype ne 'HMCCUCHN');
  1039. my ($r, $v) = split (": ", $event);
  1040. return if (!defined ($v));
  1041. return if ($ccuflags =~ /noagg/);
  1042. foreach my $rule (keys %{$hash->{hmccu}{agg}}) {
  1043. my $ftype = $hash->{hmccu}{agg}{$rule}{ftype};
  1044. my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr};
  1045. my $fread = $hash->{hmccu}{agg}{$rule}{fread};
  1046. next if ($r !~ $fread);
  1047. next if ($ftype eq 'name' && $devname !~ /$fexpr/);
  1048. next if ($ftype eq 'type' && $devhash->{ccutype} !~ /$fexpr/);
  1049. next if ($ftype eq 'group' && AttrVal ($devname, 'group', 'null') !~ /$fexpr/);
  1050. next if ($ftype eq 'room' && AttrVal ($devname, 'room', 'null') !~ /$fexpr/);
  1051. next if ($ftype eq 'alias' && AttrVal ($devname, 'alias', 'null') !~ /$fexpr/);
  1052. HMCCU_AggregateReadings ($hash, $rule);
  1053. }
  1054. }
  1055. }
  1056. return;
  1057. }
  1058. ######################################################################
  1059. # Enhance device details in FHEM WEB
  1060. ######################################################################
  1061. sub HMCCU_Detail ($$$$)
  1062. {
  1063. my ($FW_Name, $Device, $Room, $pageHash) = @_;
  1064. my $hash = $defs{$Device};
  1065. return defined ($hash->{host}) ? qq(
  1066. <span class='mkTitle'>CCU Administration</span>
  1067. <table class="block wide">
  1068. <tr class="odd">
  1069. <td><div class="col1">
  1070. &gt; <a target="_blank" href="http://$hash->{host}">CCU WebUI</a>
  1071. </div></td>
  1072. </tr>
  1073. <tr class="odd">
  1074. <td><div class="col1">
  1075. &gt; <a target="_blank" href="http://$hash->{host}/addons/cuxd/index.ccc">CUxD Config</a>
  1076. </div></td>
  1077. </tr>
  1078. </table>
  1079. ) : '';
  1080. }
  1081. ######################################################################
  1082. # Calculate reading aggregations.
  1083. # Called by Notify or via command get aggregation.
  1084. ######################################################################
  1085. sub HMCCU_AggregateReadings ($$)
  1086. {
  1087. my ($hash, $rule) = @_;
  1088. my $dc = 0;
  1089. my $mc = 0;
  1090. my $result = '';
  1091. my $rl = '';
  1092. my $table = '';
  1093. # Get rule parameters
  1094. my $ftype = $hash->{hmccu}{agg}{$rule}{ftype};
  1095. my $fexpr = $hash->{hmccu}{agg}{$rule}{fexpr};
  1096. my $fexcl = $hash->{hmccu}{agg}{$rule}{fexcl};
  1097. my $fread = $hash->{hmccu}{agg}{$rule}{fread};
  1098. my $fcond = $hash->{hmccu}{agg}{$rule}{fcond};
  1099. my $ftrue = $hash->{hmccu}{agg}{$rule}{ftrue};
  1100. my $felse = $hash->{hmccu}{agg}{$rule}{felse};
  1101. my $fpref = $hash->{hmccu}{agg}{$rule}{fpref};
  1102. my $fhtml = exists ($hash->{hmccu}{agg}{$rule}{fhtml}) ? 1 : 0;
  1103. my $resval;
  1104. $resval = $ftrue if ($fcond =~ /^(max|min|sum|avg)$/);
  1105. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  1106. foreach my $d (@devlist) {
  1107. my $ch = $defs{$d};
  1108. my $cn = $ch->{NAME};
  1109. my $ct = $ch->{TYPE};
  1110. my $fmatch = '';
  1111. $fmatch = $cn if ($ftype eq 'name');
  1112. $fmatch = $ch->{ccutype} if ($ftype eq 'type');
  1113. $fmatch = AttrVal ($cn, 'group', '') if ($ftype eq 'group');
  1114. $fmatch = AttrVal ($cn, 'room', '') if ($ftype eq 'room');
  1115. $fmatch = AttrVal ($cn, 'alias', '') if ($ftype eq 'alias');
  1116. next if ($fmatch eq '' || $fmatch !~ /$fexpr/ || ($fexcl ne '' && $fmatch =~ /$fexcl/));
  1117. my $fcoll = $hash->{hmccu}{agg}{$rule}{fcoll} eq 'NAME' ?
  1118. $cn : AttrVal ($cn, $hash->{hmccu}{agg}{$rule}{fcoll}, $cn);
  1119. # Compare readings
  1120. foreach my $r (keys %{$ch->{READINGS}}) {
  1121. next if ($r !~ /$fread/);
  1122. my $rv = $ch->{READINGS}{$r}{VAL};
  1123. my $f = 0;
  1124. if (($fcond eq 'any' || $fcond eq 'all') && $rv =~ /$ftrue/) {
  1125. $mc++;
  1126. $f = 1;
  1127. }
  1128. if ($fcond eq 'max' && $rv > $resval) {
  1129. $resval = $rv;
  1130. $mc = 1;
  1131. $f = 1;
  1132. }
  1133. if ($fcond eq 'min' && $rv < $resval) {
  1134. $resval = $rv;
  1135. $mc = 1;
  1136. $f = 1;
  1137. }
  1138. if ($fcond eq 'sum' || $fcond eq 'avg') {
  1139. $resval += $rv;
  1140. $mc++;
  1141. $f = 1;
  1142. }
  1143. if (($fcond eq 'gt' && $rv > $ftrue) ||
  1144. ($fcond eq 'lt' && $rv < $ftrue) ||
  1145. ($fcond eq 'ge' && $rv >= $ftrue) ||
  1146. ($fcond eq 'le' && $rv <= $ftrue)) {
  1147. $mc++;
  1148. $f = 1;
  1149. }
  1150. if ($f) {
  1151. $rl .= ($mc > 1 ? ",$fcoll" : $fcoll);
  1152. last;
  1153. }
  1154. }
  1155. $dc++;
  1156. }
  1157. $rl = $hash->{hmccu}{agg}{$rule}{fdflt} if ($rl eq '');
  1158. # HTML code generation
  1159. if ($fhtml) {
  1160. if ($rl ne '') {
  1161. $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}.
  1162. $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-table'};
  1163. $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'header'}
  1164. if (exists ($hash->{hmccu}{agg}{$rule}{fhtml}{'header'}));
  1165. my $row = 1;
  1166. foreach my $v (split (",", $rl)) {
  1167. my $t_row = ($row % 2) ? $hash->{hmccu}{agg}{$rule}{fhtml}{'row-odd'} :
  1168. $hash->{hmccu}{agg}{$rule}{fhtml}{'row-even'};
  1169. $t_row =~ s/\<reading\/\>/$v/;
  1170. $table .= $t_row;
  1171. $row++;
  1172. }
  1173. $table .= $hash->{hmccu}{agg}{$rule}{fhtml}{'end-table'}.
  1174. $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'};
  1175. }
  1176. else {
  1177. $table = $hash->{hmccu}{agg}{$rule}{fhtml}{'begin-html'}.
  1178. $hash->{hmccu}{agg}{$rule}{fhtml}{'default'}.
  1179. $hash->{hmccu}{agg}{$rule}{fhtml}{'end-html'};
  1180. }
  1181. }
  1182. if ($fcond eq 'any') {
  1183. $result = $mc > 0 ? $ftrue : $felse;
  1184. }
  1185. elsif ($fcond eq 'all') {
  1186. $result = $mc == $dc ? $ftrue : $felse;
  1187. }
  1188. elsif ($fcond eq 'min' || $fcond eq 'max' || $fcond eq 'sum') {
  1189. $result = $mc > 0 ? $resval : $felse;
  1190. }
  1191. elsif ($fcond eq 'avg') {
  1192. $result = $mc > 0 ? $resval/$mc : $felse;
  1193. }
  1194. elsif ($fcond =~ /^(gt|lt|ge|le)$/) {
  1195. $result = $mc;
  1196. }
  1197. # Set readings
  1198. readingsBeginUpdate ($hash);
  1199. readingsBulkUpdate ($hash, $fpref.'state', $result);
  1200. readingsBulkUpdate ($hash, $fpref.'match', $mc);
  1201. readingsBulkUpdate ($hash, $fpref.'count', $dc);
  1202. readingsBulkUpdate ($hash, $fpref.'list', $rl);
  1203. readingsBulkUpdate ($hash, $fpref.'table', $table) if ($fhtml);
  1204. readingsEndUpdate ($hash, 1);
  1205. return $result;
  1206. }
  1207. ######################################################################
  1208. # Delete device
  1209. ######################################################################
  1210. sub HMCCU_Undef ($$)
  1211. {
  1212. my ($hash, $arg) = @_;
  1213. # Shutdown RPC server
  1214. HMCCU_Shutdown ($hash);
  1215. # Delete reference to IO module in client devices
  1216. my @keylist = keys %defs;
  1217. foreach my $d (@keylist) {
  1218. if (exists ($defs{$d}) && exists($defs{$d}{IODev}) &&
  1219. $defs{$d}{IODev} == $hash) {
  1220. delete $defs{$d}{IODev};
  1221. }
  1222. }
  1223. return undef;
  1224. }
  1225. ######################################################################
  1226. # Shutdown FHEM
  1227. ######################################################################
  1228. sub HMCCU_Shutdown ($)
  1229. {
  1230. my ($hash) = @_;
  1231. my $name = $hash->{NAME};
  1232. # Shutdown RPC server
  1233. if (HMCCU_IsFlag ($name, "(extrpc|procrpc)")) {
  1234. HMCCU_StopExtRPCServer ($hash);
  1235. }
  1236. else {
  1237. HMCCU_StopRPCServer ($hash);
  1238. }
  1239. # Remove existing timer functions
  1240. RemoveInternalTimer ($hash);
  1241. return undef;
  1242. }
  1243. ######################################################################
  1244. # Set commands
  1245. ######################################################################
  1246. sub HMCCU_Set ($@)
  1247. {
  1248. my ($hash, $a, $h) = @_;
  1249. my $name = shift @$a;
  1250. my $opt = shift @$a;
  1251. my $options = "var delete execute hmscript cleardefaults:noArg defaults:noArg ".
  1252. "importdefaults rpcregister:all rpcserver:on,off,restart ackmessages:noArg";
  1253. my @ifList = HMCCU_GetRPCInterfaceList ($hash);
  1254. if (scalar (@ifList) > 0) {
  1255. my $ifStr = join (',', @ifList);
  1256. $options =~ s/register:all/rpcregister:all,$ifStr/;
  1257. }
  1258. my $usage = "HMCCU: Unknown argument $opt, choose one of $options";
  1259. my $host = $hash->{host};
  1260. return "HMCCU: I/O device not initialized. Try again later." if ($hash->{hmccu}{ccu}{delayed});
  1261. return "HMCCU: CCU busy, choose one of rpcserver:off"
  1262. if ($opt ne 'rpcserver' && HMCCU_IsRPCStateBlocking ($hash));
  1263. my $ccuflags = HMCCU_GetFlags ($name);
  1264. my $stripchar = AttrVal ($name, "stripchar", '');
  1265. my $ccureadings = AttrVal ($name, "ccureadings", $ccuflags =~ /noReadings/ ? 0 : 1);
  1266. my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  1267. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hash);
  1268. my $substitute = HMCCU_GetAttrSubstitute ($hash, $hash);
  1269. my $result;
  1270. # Add program names to command execute
  1271. if (exists ($hash->{hmccu}{prg})) {
  1272. my @progs = ();
  1273. foreach my $p (keys %{$hash->{hmccu}{prg}}) {
  1274. push (@progs, $p) if ($hash->{hmccu}{prg}{$p}{internal} eq 'false' && $p !~ /^\$/);
  1275. }
  1276. if (scalar (@progs) > 0) {
  1277. my $prgopt = "execute:".join(',', @progs);
  1278. $options =~ s/execute/$prgopt/;
  1279. $usage =~ s/execute/$prgopt/;
  1280. }
  1281. }
  1282. if ($opt eq 'var') {
  1283. my $vartype;
  1284. $vartype = shift @$a if (scalar (@$a) == 3);
  1285. my $objname = shift @$a;
  1286. my $objvalue = shift @$a;
  1287. $usage = "set $name $opt [{'bool'|'list'|'number'|'test'}] variable value [param=value [...]]";
  1288. return HMCCU_SetError ($hash, $usage) if (!defined ($objvalue));
  1289. $objname =~ s/$stripchar$// if ($stripchar ne '');
  1290. $objvalue =~ s/\\_/%20/g;
  1291. $h->{name} = $objname if (!defined ($h) && defined ($vartype));
  1292. $result = HMCCU_SetVariable ($hash, $objname, $objvalue, $vartype, $h);
  1293. return HMCCU_SetError ($hash, $result) if ($result < 0);
  1294. return HMCCU_SetState ($hash, "OK");
  1295. }
  1296. elsif ($opt eq 'datapoint') {
  1297. return HMCCU_SetError ($hash, "Command set datapoint is no longer supported by I/O device");
  1298. }
  1299. elsif ($opt eq 'delete') {
  1300. my $objname = shift @$a;
  1301. my $objtype = shift @$a;
  1302. $objtype = "OT_VARDP" if (!defined ($objtype));
  1303. $usage = "Usage: set $name $opt ccuobject ['OT_VARDP'|'OT_DEVICE']";
  1304. return HMCCU_SetError ($hash, $usage)
  1305. if (!defined ($objname) || $objtype !~ /^(OT_VARDP|OT_DEVICE)$/);
  1306. $result = HMCCU_HMScriptExt ($hash, "!DeleteObject", { name => $objname, type => $objtype });
  1307. return HMCCU_SetError ($hash, -2) if ($result =~ /^ERROR:.*/);
  1308. return HMCCU_SetState ($hash, "OK");
  1309. }
  1310. elsif ($opt eq 'execute') {
  1311. my $program = shift @$a;
  1312. my $response;
  1313. $usage = "Usage: set $name $opt program-name";
  1314. return HMCCU_SetError ($hash, $usage) if (!defined ($program));
  1315. my $cmd = qq(dom.GetObject("$program").ProgramExecute());
  1316. my $value = HMCCU_HMCommand ($hash, $cmd, 0);
  1317. return HMCCU_SetState ($hash, "OK") if (defined ($value));
  1318. return HMCCU_SetError ($hash, "Program execution error");
  1319. }
  1320. elsif ($opt eq 'hmscript') {
  1321. my $script = shift @$a;
  1322. my $dump = shift @$a;
  1323. my $response = '';
  1324. my %objects = ();
  1325. my $objcount = 0;
  1326. $usage = "Usage: set $name $opt {file|!function|'['code']'} ['dump'] [parname=value [...]]";
  1327. # If no parameter is specified list available script functions
  1328. if (!defined ($script)) {
  1329. $response = "Available HomeMatic script functions:\n".
  1330. "-------------------------------------\n";
  1331. foreach my $scr (keys %{$HMCCU_SCRIPTS}) {
  1332. $response .= "$scr ".$HMCCU_SCRIPTS->{$scr}{syntax}."\n".
  1333. $HMCCU_SCRIPTS->{$scr}{description}."\n\n";
  1334. }
  1335. $response .= $usage;
  1336. return $response;
  1337. }
  1338. return HMCCU_SetError ($hash, $usage) if (defined ($dump) && $dump ne 'dump');
  1339. # Execute script
  1340. $response = HMCCU_HMScriptExt ($hash, $script, $h);
  1341. return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/);
  1342. HMCCU_SetState ($hash, "OK");
  1343. return $response if (! $ccureadings || defined ($dump));
  1344. foreach my $line (split /\n/, $response) {
  1345. my @tokens = split /=/, $line;
  1346. next if (@tokens != 2);
  1347. my $reading;
  1348. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $tokens[0],
  1349. $HMCCU_FLAG_INTERFACE);
  1350. ($add, $chn) = HMCCU_GetAddress ($hash, $nam, '', '') if ($flags == $HMCCU_FLAGS_NCD);
  1351. if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) {
  1352. $objects{$add}{$chn}{$dpt} = $tokens[1];
  1353. $objcount++;
  1354. }
  1355. else {
  1356. # If output is not related to a channel store reading in I/O device
  1357. my $Value = HMCCU_Substitute ($tokens[1], $substitute, 0, undef, $tokens[0]);
  1358. my $rn = $tokens[0];
  1359. $rn =~ s/\:/\./g;
  1360. $rn =~ s/[^A-Za-z\d_\.-]+/_/g;
  1361. readingsSingleUpdate ($hash, $rn, $Value, 1);
  1362. }
  1363. }
  1364. HMCCU_UpdateMultipleDevices ($hash, \%objects) if ($objcount > 0);
  1365. return defined ($dump) ? $response : undef;
  1366. }
  1367. elsif ($opt eq 'rpcregister') {
  1368. return HMCCU_SetError ($hash, "HMCCU: Command not supported by internal RPC server")
  1369. if ($ccuflags !~ /procrpc/);
  1370. my $ifName = shift @$a;
  1371. $result = '';
  1372. @ifList = (defined ($ifName) && $ifName ne 'all') ? ($ifName) : HMCCU_GetRPCInterfaceList ($hash);
  1373. foreach my $i (@ifList) {
  1374. my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $i);
  1375. if ($rpcdev eq '') {
  1376. Log3 $name, 2, "HMCCU: Can't find HMCCURPCPROC device for interface $i";
  1377. next;
  1378. }
  1379. my $res = AnalyzeCommandChain (undef, "set $rpcdev register");
  1380. $result .= $res if (defined ($res));
  1381. }
  1382. return HMCCU_SetState ($hash, "OK", $result);
  1383. }
  1384. elsif ($opt eq 'rpcserver') {
  1385. my $action = shift @$a;
  1386. $action = shift @$a if ($action eq $opt);
  1387. $usage = "Usage: set $name $opt {'on'|'off'|'restart'}";
  1388. return HMCCU_SetError ($hash, $usage)
  1389. if (!defined ($action) || $action !~ /^(on|off|restart)$/);
  1390. if ($action eq 'on') {
  1391. if ($ccuflags =~ /(extrpc|procrpc)/) {
  1392. return HMCCU_SetError ($hash, "Start of RPC server failed")
  1393. if (!HMCCU_StartExtRPCServer ($hash));
  1394. }
  1395. else {
  1396. return HMCCU_SetError ($hash, "Start of RPC server failed")
  1397. if (!HMCCU_StartIntRPCServer ($hash));
  1398. }
  1399. }
  1400. elsif ($action eq 'off') {
  1401. if ($ccuflags =~ /(extrpc|procrpc)/) {
  1402. return HMCCU_SetError ($hash, "Stop of RPC server failed")
  1403. if (!HMCCU_StopExtRPCServer ($hash));
  1404. }
  1405. else {
  1406. return HMCCU_SetError ($hash, "Stop of RPC server failed")
  1407. if (!HMCCU_StopRPCServer ($hash));
  1408. }
  1409. }
  1410. elsif ($action eq 'restart') {
  1411. return "HMCCU: No RPC server running" if (!HMCCU_IsRPCServerRunning ($hash, undef, undef));
  1412. if ($ccuflags !~ /(extrpc|procrpc)/) {
  1413. return "HMCCU: Can't stop RPC server" if (!HMCCURPC_StopRPCServer ($hash));
  1414. HMCCU_SetRPCState ($hash, 'restarting');
  1415. DoTrigger ($name, "RPC server restarting");
  1416. }
  1417. else {
  1418. return HMCCU_SetError ($hash, "HMCCU: restart not supported by external RPC server");
  1419. }
  1420. }
  1421. return HMCCU_SetState ($hash, "OK");
  1422. }
  1423. elsif ($opt eq 'ackmessages') {
  1424. my $response = HMCCU_HMScriptExt ($hash, "!ClearUnreachable", undef);
  1425. return HMCCU_SetError ($hash, -2, $response) if ($response =~ /^ERROR:/);
  1426. return HMCCU_SetState ($hash, "OK", "Unreach errors in CCU cleared");
  1427. }
  1428. elsif ($opt eq 'defaults') {
  1429. my $rc = HMCCU_SetDefaults ($hash);
  1430. return HMCCU_SetError ($hash, "HMCCU: No default attributes found") if ($rc == 0);
  1431. return HMCCU_SetState ($hash, "OK");
  1432. }
  1433. elsif ($opt eq 'cleardefaults') {
  1434. %HMCCU_CUST_CHN_DEFAULTS = ();
  1435. %HMCCU_CUST_DEV_DEFAULTS = ();
  1436. return HMCCU_SetState ($hash, "OK", "Default attributes deleted");
  1437. }
  1438. elsif ($opt eq 'importdefaults') {
  1439. my $filename = shift @$a;
  1440. $usage = "Usage: set $name $opt filename";
  1441. return HMCCU_SetError ($hash, $usage) if (!defined ($filename));
  1442. my $rc = HMCCU_ImportDefaults ($filename);
  1443. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  1444. if ($rc < 0) {
  1445. $rc = -$rc;
  1446. return HMCCU_SetError ($hash, "Syntax error in default attribute file $filename line $rc");
  1447. }
  1448. return HMCCU_SetState ($hash, "OK", "Default attributes read from file $filename");
  1449. }
  1450. else {
  1451. return $usage;
  1452. }
  1453. }
  1454. ######################################################################
  1455. # Get commands
  1456. ######################################################################
  1457. sub HMCCU_Get ($@)
  1458. {
  1459. my ($hash, $a, $h) = @_;
  1460. my $name = shift @$a;
  1461. my $opt = shift @$a;
  1462. my $options = "defaults:noArg exportdefaults devicelist dump dutycycle:noArg vars update".
  1463. " updateccu parfile configdesc firmware rpcevents:noArg rpcstate:noArg deviceinfo";
  1464. my $usage = "HMCCU: Unknown argument $opt, choose one of $options";
  1465. my $host = $hash->{host};
  1466. return "HMCCU: I/O device not initialized. Try again later." if ($hash->{hmccu}{ccu}{delayed});
  1467. return "HMCCU: CCU busy, choose one of rpcstate:noArg"
  1468. if ($opt ne 'rpcstate' && HMCCU_IsRPCStateBlocking ($hash));
  1469. my $ccuflags = HMCCU_GetFlags ($name);
  1470. my $ccureadings = AttrVal ($name, "ccureadings", $ccuflags =~ /noReadings/ ? 0 : 1);
  1471. my $parfile = AttrVal ($name, "parfile", '');
  1472. my $readname;
  1473. my $readaddr;
  1474. my $result = '';
  1475. my $rc;
  1476. if ($opt eq 'dump') {
  1477. my $content = shift @$a;
  1478. my $filter = shift @$a;
  1479. $filter = '.*' if (!defined ($filter));
  1480. $usage = "Usage: get $name dump {'datapoints'|'devtypes'} [filter]";
  1481. my %foper = (1, "R", 2, "W", 4, "E", 3, "RW", 5, "RE", 6, "WE", 7, "RWE");
  1482. my %ftype = (2, "B", 4, "F", 16, "I", 20, "S");
  1483. return HMCCU_SetError ($hash, $usage) if (!defined ($content));
  1484. if ($content eq 'devtypes') {
  1485. foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) {
  1486. $result .= $devtype."\n" if ($devtype =~ /$filter/);
  1487. }
  1488. }
  1489. elsif ($content eq 'datapoints') {
  1490. foreach my $devtype (sort keys %{$hash->{hmccu}{dp}}) {
  1491. next if ($devtype !~ /$filter/);
  1492. foreach my $chn (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}}) {
  1493. foreach my $dpt (sort keys %{$hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  1494. my $t = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{type};
  1495. my $o = $hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dpt}{oper};
  1496. $result .= $devtype.".".$chn.".".$dpt." [".
  1497. (exists($ftype{$t}) ? $ftype{$t} : $t)."] [".
  1498. (exists($foper{$o}) ? $foper{$o} : $o)."]\n";
  1499. }
  1500. }
  1501. }
  1502. }
  1503. else {
  1504. return HMCCU_SetError ($hash, $usage);
  1505. }
  1506. return HMCCU_SetState ($hash, "OK", ($result eq '') ? "No data found" : $result);
  1507. }
  1508. elsif ($opt eq 'vars') {
  1509. my $varname = shift @$a;
  1510. $usage = "Usage: get $name vars {regexp}[,...]";
  1511. return HMCCU_SetError ($hash, $usage) if (!defined ($varname));
  1512. ($rc, $result) = HMCCU_GetVariables ($hash, $varname);
  1513. return HMCCU_SetError ($hash, $rc, $result) if ($rc < 0);
  1514. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1515. }
  1516. elsif ($opt eq 'update' || $opt eq 'updateccu') {
  1517. my $devexp = shift @$a;
  1518. $devexp = '.*' if (!defined ($devexp));
  1519. $usage = "Usage: get $name $opt [device-expr [{'State'|'Value'}]]";
  1520. my $ccuget = shift @$a;
  1521. $ccuget = 'Attr' if (!defined ($ccuget));
  1522. return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/);
  1523. my ($co, $ce) = HMCCU_UpdateClients ($hash, $devexp, $ccuget, ($opt eq 'updateccu') ? 1 : 0, undef);
  1524. return HMCCU_SetState ($hash, "OK",
  1525. "$co client devices successfully updated. Update for $ce client devices failed");
  1526. }
  1527. elsif ($opt eq 'parfile') {
  1528. my $par_parfile = shift @$a;
  1529. my @parameters;
  1530. my $parcount;
  1531. if (defined ($par_parfile)) {
  1532. $parfile = $par_parfile;
  1533. }
  1534. else {
  1535. return HMCCU_SetError ($hash, "No parameter file specified") if ($parfile eq '');
  1536. }
  1537. # Read parameter file
  1538. if (open (PARFILE, "<$parfile")) {
  1539. @parameters = <PARFILE>;
  1540. $parcount = scalar @parameters;
  1541. close (PARFILE);
  1542. }
  1543. else {
  1544. return HMCCU_SetError ($hash, -16);
  1545. }
  1546. return HMCCU_SetError ($hash, "Empty parameter file") if ($parcount < 1);
  1547. ($rc, $result) = HMCCU_GetChannel ($hash, \@parameters);
  1548. return HMCCU_SetError ($hash, $rc) if ($rc < 0);
  1549. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1550. }
  1551. elsif ($opt eq 'deviceinfo') {
  1552. my $device = shift @$a;
  1553. $usage = "Usage: get $name $opt device [{'State'|'Value'}]";
  1554. return HMCCU_SetError ($hash, $usage) if (!defined ($device));
  1555. my $ccuget = shift @$a;
  1556. $ccuget = 'Attr' if (!defined ($ccuget));
  1557. return HMCCU_SetError ($hash, $usage) if ($ccuget !~ /^(Attr|State|Value)$/);
  1558. return HMCCU_SetError ($hash, -1) if (!HMCCU_IsValidDeviceOrChannel ($hash, $device, $HMCCU_FL_ALL));
  1559. $result = HMCCU_GetDeviceInfo ($hash, $device, $ccuget);
  1560. return HMCCU_SetError ($hash, -2) if ($result eq '' || $result =~ /^ERROR:.*/);
  1561. HMCCU_SetState ($hash, "OK");
  1562. return HMCCU_FormatDeviceInfo ($result);
  1563. }
  1564. elsif ($opt eq 'rpcevents') {
  1565. if ($ccuflags =~ /(extrpc|procrpc)/) {
  1566. # my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, undef);
  1567. # return HMCCU_SetError ($hash, "HMCCU: External RPC server not found") if ($rpcdev eq '');
  1568. # $result = AnalyzeCommandChain (undef, "get $rpcdev rpcevents");
  1569. # return HMCCU_SetState ($hash, "OK", $result) if (defined ($result));
  1570. # return HMCCU_SetError ($hash, "No event statistics available");
  1571. # }
  1572. # elsif ($ccuflags =~ /procrpc/) {
  1573. $result = '';
  1574. my @iflist = HMCCU_GetRPCInterfaceList ($hash);
  1575. foreach my $ifname (@iflist) {
  1576. my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname);
  1577. if ($rpcdev eq '') {
  1578. Log3 $name, 2, "HMCCU: Can't find HMCCURPCPROC device for interface $ifname";
  1579. next;
  1580. }
  1581. my $res = AnalyzeCommandChain (undef, "get $rpcdev rpcevents");
  1582. $result .= $res if (defined ($result));
  1583. }
  1584. return HMCCU_SetState ($hash, "OK", $result) if ($result ne '');
  1585. return HMCCU_SetError ($hash, "No event statistics available");
  1586. }
  1587. else {
  1588. return HMCCU_SetError ($hash, "No event statistics available")
  1589. if (!exists ($hash->{hmccu}{evs}) || !exists ($hash->{hmccu}{evr}));
  1590. foreach my $stkey (sort keys %{$hash->{hmccu}{evr}}) {
  1591. $result .= "S: ".$stkey." = ".$hash->{hmccu}{evs}{$stkey}."\n";
  1592. $result .= "R: ".$stkey." = ".$hash->{hmccu}{evr}{$stkey}."\n";
  1593. }
  1594. return HMCCU_SetState ($hash, "OK", $result);
  1595. }
  1596. }
  1597. elsif ($opt eq 'rpcstate') {
  1598. my @hm_pids = ();
  1599. my @hm_tids = ();
  1600. $result = "No RPC processes or threads are running";
  1601. if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) {
  1602. $result = "RPC process(es) running with pid(s) ".
  1603. join (',', @hm_pids) if (scalar (@hm_pids) > 0);
  1604. $result = "RPC thread(s) running with tid(s) ".
  1605. join (',', @hm_tids) if (scalar (@hm_tids) > 0);
  1606. }
  1607. return HMCCU_SetState ($hash, "OK", $result);
  1608. }
  1609. elsif ($opt eq 'devicelist') {
  1610. my ($devcount, $chncount, $ifcount, $prgcount, $gcount) = HMCCU_GetDeviceList ($hash);
  1611. return HMCCU_SetError ($hash, -2) if ($devcount < 0);
  1612. return HMCCU_SetError ($hash, "No devices received from CCU") if ($devcount == 0);
  1613. $result = "Read $devcount devices with $chncount channels from CCU";
  1614. my $optcmd = shift @$a;
  1615. if (defined ($optcmd)) {
  1616. if ($optcmd eq 'dump') {
  1617. $result .= "\n-----------------------------------------\n";
  1618. my $n = 0;
  1619. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  1620. if ($hash->{hmccu}{dev}{$add}{addtype} eq 'dev') {
  1621. $result .= "Device ".'"'.$hash->{hmccu}{dev}{$add}{name}.'"'." [".$add."] ".
  1622. "Type=".$hash->{hmccu}{dev}{$add}{type}."\n";
  1623. $n = 0;
  1624. }
  1625. else {
  1626. $result .= " Channel $n ".'"'.$hash->{hmccu}{dev}{$add}{name}.'"'.
  1627. " [".$add."]\n";
  1628. $n++;
  1629. }
  1630. }
  1631. return $result;
  1632. }
  1633. elsif ($optcmd eq 'create') {
  1634. $usage = "Usage: get $name create {devexp|chnexp} [t={'chn'|'dev'|'all'}] [s=suffix] ".
  1635. "[p=prefix] [f=format] ['defattr'] ['duplicates'] [save] [attr=val [...]]";
  1636. my $devdefaults = 0;
  1637. my $duplicates = 0;
  1638. my $savedef = 0;
  1639. my $newcount = 0;
  1640. # Process command line parameters
  1641. my $devspec = shift @$a;
  1642. my $devprefix = exists ($h->{p}) ? $h->{p} : '';
  1643. my $devsuffix = exists ($h->{'s'}) ? $h->{'s'} : '';
  1644. my $devtype = exists ($h->{t}) ? $h->{t} : 'dev';
  1645. my $devformat = exists ($h->{f}) ? $h->{f} : '%n';
  1646. return HMCCU_SetError ($hash, $usage)
  1647. if ($devtype !~ /^(dev|chn|all)$/ || !defined ($devspec));
  1648. foreach my $defopt (@$a) {
  1649. if ($defopt eq 'defattr') { $devdefaults = 1; }
  1650. elsif ($defopt eq 'duplicates') { $duplicates = 1; }
  1651. elsif ($defopt eq 'save') { $savedef = 1; }
  1652. else { return HMCCU_SetError ($hash, $usage); }
  1653. }
  1654. # Get list of existing client devices
  1655. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  1656. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  1657. my $defmod = $hash->{hmccu}{dev}{$add}{addtype} eq 'dev' ? 'HMCCUDEV' : 'HMCCUCHN';
  1658. my $ccuname = $hash->{hmccu}{dev}{$add}{name};
  1659. my $ccudevname = HMCCU_GetDeviceName ($hash, $add, $ccuname);
  1660. next if ($devtype ne 'all' && $devtype ne $hash->{hmccu}{dev}{$add}{addtype});
  1661. next if (HMCCU_ExprNotMatch ($ccuname, $devspec, 1));
  1662. # Build FHEM device name
  1663. my $devname = $devformat;
  1664. $devname = $devprefix.$devname.$devsuffix;
  1665. $devname =~ s/%n/$ccuname/g;
  1666. $devname =~ s/%d/$ccudevname/g;
  1667. $devname =~ s/%a/$add/g;
  1668. $devname =~ s/[^A-Za-z\d_\.]+/_/g;
  1669. # Check for duplicate device definitions
  1670. if (!$duplicates) {
  1671. next if (exists ($defs{$devname}));
  1672. my $devexists = 0;
  1673. foreach my $exdev (@devlist) {
  1674. if ($defs{$exdev}->{ccuaddr} eq $add) {
  1675. $devexists = 1;
  1676. last;
  1677. }
  1678. }
  1679. next if ($devexists);
  1680. }
  1681. # Define new client device
  1682. my $ret = CommandDefine (undef, $devname." $defmod ".$add);
  1683. if ($ret) {
  1684. Log3 $name, 2, "HMCCU: Define command failed $devname $defmod $ccuname";
  1685. Log3 $name, 2, "$defmod: $ret";
  1686. $result .= "\nCan't create device $devname. $ret";
  1687. next;
  1688. }
  1689. # Set device attributes
  1690. HMCCU_SetDefaults ($defs{$devname}) if ($devdefaults);
  1691. foreach my $da (keys %$h) {
  1692. next if ($da =~ /^[pstf]$/);
  1693. $ret = CommandAttr (undef, "$devname $da ".$h->{$da});
  1694. if ($ret) {
  1695. Log3 $name, 2, "HMCCU: Attr command failed $devname $da ".$h->{$da};
  1696. Log3 $name, 2, "$defmod: $ret";
  1697. }
  1698. }
  1699. Log3 $name, 2, "$defmod: Created device $devname";
  1700. $result .= "\nCreated device $devname";
  1701. $newcount++;
  1702. }
  1703. CommandSave (undef, undef) if ($newcount > 0 && $savedef);
  1704. $result .= "\nCreated $newcount client devices";
  1705. }
  1706. }
  1707. return HMCCU_SetState ($hash, "OK", $result);
  1708. }
  1709. elsif ($opt eq 'dutycycle') {
  1710. my $dc = HMCCU_GetDutyCycle ($hash);
  1711. return HMCCU_SetState ($hash, "OK", "Read $dc duty cycle values");
  1712. }
  1713. elsif ($opt eq 'firmware') {
  1714. my $devtype = shift @$a;
  1715. $devtype = '.*' if (!defined ($devtype));
  1716. my $dtexp = $devtype;
  1717. $dtexp = '.*' if ($devtype eq 'full');
  1718. my $dc = HMCCU_GetFirmwareVersions ($hash, $dtexp);
  1719. return "Found no firmware downloads" if ($dc == 0);
  1720. $result = "Found $dc firmware downloads. Click on the new version number for download\n\n";
  1721. if ($devtype eq 'full') {
  1722. $result .=
  1723. "Type Available Date\n".
  1724. "-----------------------------------------\n";
  1725. foreach my $ct (keys %{$hash->{hmccu}{type}}) {
  1726. $result .= sprintf "%-20s <a href=\"http://www.eq-3.de/%s\">%-9s</a> %-10s\n",
  1727. $ct, $hash->{hmccu}{type}{$ct}{download},
  1728. $hash->{hmccu}{type}{$ct}{firmware}, $hash->{hmccu}{type}{$ct}{date};
  1729. }
  1730. }
  1731. else {
  1732. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  1733. return $result if (scalar (@devlist) == 0);
  1734. $result .=
  1735. "Device Type Current Available Date\n".
  1736. "---------------------------------------------------------------------------\n";
  1737. foreach my $dev (@devlist) {
  1738. my $ch = $defs{$dev};
  1739. my $ct = uc($ch->{ccutype});
  1740. my $fw = defined ($ch->{firmware}) ? $ch->{firmware} : 'N/A';
  1741. next if (!exists ($hash->{hmccu}{type}{$ct}) || $ct !~ /$dtexp/);
  1742. $result .= sprintf "%-25s %-20s %-7s <a href=\"http://www.eq-3.de/%s\">%-9s</a> %-10s\n",
  1743. $ch->{NAME}, $ct, $fw, $hash->{hmccu}{type}{$ct}{download},
  1744. $hash->{hmccu}{type}{$ct}{firmware}, $hash->{hmccu}{type}{$ct}{date};
  1745. }
  1746. }
  1747. return HMCCU_SetState ($hash, "OK", $result);
  1748. }
  1749. elsif ($opt eq 'defaults') {
  1750. $result = HMCCU_GetDefaults ($hash, 1);
  1751. return HMCCU_SetState ($hash, "OK", $result);
  1752. }
  1753. elsif ($opt eq 'exportdefaults') {
  1754. my $filename = shift @$a;
  1755. $usage = "Usage: get $name $opt filename ['all'] ['csv']";
  1756. my $csv = 0;
  1757. my $all = 0;
  1758. foreach my $defopt (@$a) {
  1759. if ($defopt eq 'csv') { $csv = 1; }
  1760. elsif ($defopt eq 'all') { $all = 1; }
  1761. else { return HMCCU_SetError ($hash, $usage); }
  1762. }
  1763. return HMCCU_SetError ($hash, $usage) if (!defined ($filename));
  1764. my $rc = $csv ? HMCCU_ExportDefaultsCSV ($filename, $all) : HMCCU_ExportDefaults ($filename, $all);
  1765. return HMCCU_SetError ($hash, -16) if ($rc == 0);
  1766. return HMCCU_SetState ($hash, "OK", "Default attributes written to $filename");
  1767. }
  1768. elsif ($opt eq 'aggregation') {
  1769. my $rule = shift @$a;
  1770. $usage = "Usage: get $name $opt {'all'|'rule'}";
  1771. return HMCCU_SetError ($hash, $usage) if (!defined ($rule));
  1772. if ($rule eq 'all') {
  1773. foreach my $r (keys %{$hash->{hmccu}{agg}}) {
  1774. my $rc = HMCCU_AggregateReadings ($hash, $r);
  1775. $result .= "$r = $rc\n";
  1776. }
  1777. }
  1778. else {
  1779. return HMCCU_SetError ($hash, "HMCCU: Aggregation rule does not exist")
  1780. if (!exists ($hash->{hmccu}{agg}{$rule}));
  1781. $result = HMCCU_AggregateReadings ($hash, $rule);
  1782. $result = "$rule = $result";
  1783. }
  1784. return HMCCU_SetState ($hash, "OK", $ccureadings ? undef : $result);
  1785. }
  1786. elsif ($opt eq 'configdesc') {
  1787. my $ccuobj = shift @$a;
  1788. $usage = "Usage: get $name $opt {device|channel}";
  1789. return HMCCU_SetError ($hash, $usage) if (!defined ($ccuobj));
  1790. my ($rc, $res) = HMCCU_RPCGetConfig ($hash, $ccuobj, "getParamsetDescription", undef);
  1791. return HMCCU_SetError ($hash, $rc) if ($rc < 0);
  1792. return HMCCU_SetState ($hash, "OK", $res);
  1793. }
  1794. else {
  1795. if (exists ($hash->{hmccu}{agg})) {
  1796. my @rules = keys %{$hash->{hmccu}{agg}};
  1797. $usage .= " aggregation:all,".join (',', @rules) if (scalar (@rules) > 0);
  1798. }
  1799. return $usage;
  1800. }
  1801. }
  1802. ######################################################################
  1803. # Parse CCU object specification.
  1804. #
  1805. # Supported address types:
  1806. # Classic Homematic and Homematic-IP addresses.
  1807. # Team addresses with leading * for BidCos-RF.
  1808. # CCU virtual remote addresses (BidCoS:ChnNo)
  1809. # OSRAM lightify addresses (OL-...)
  1810. # Homematic virtual layer addresses (if known by HMCCU)
  1811. #
  1812. # Possible syntax for datapoints:
  1813. # Interface.Address:Channel.Datapoint
  1814. # Address:Channel.Datapoint
  1815. # Channelname.Datapoint
  1816. #
  1817. # Possible syntax for channels:
  1818. # Interface.Address:Channel
  1819. # Address:Channel
  1820. # Channelname
  1821. #
  1822. # If object name doesn't match the rules above it's treated as name.
  1823. # With parameter flags one can specify if result is filled up with
  1824. # default values for interface or datapoint.
  1825. #
  1826. # Return list of detected attributes (empty string if attribute is
  1827. # not detected):
  1828. # (Interface, Address, Channel, Datapoint, Name, Flags)
  1829. # Flags is a bitmask of detected attributes.
  1830. ######################################################################
  1831. sub HMCCU_ParseObject ($$$)
  1832. {
  1833. my ($hash, $object, $flags) = @_;
  1834. my ($i, $a, $c, $d, $n, $f) = ('', '', '', '', '', '', 0);
  1835. my $extaddr;
  1836. # "ccu:" is default. Remove it.
  1837. $object =~ s/^ccu://g;
  1838. # Check for FHEM device
  1839. if ($object =~ /^hmccu:/) {
  1840. my ($hmccu, $fhdev, $fhcdp) = split(':', $object);
  1841. return ($i, $a, $c, $d, $n, $f) if (!defined ($fhdev));
  1842. my $cl_hash = $defs{$fhdev};
  1843. return ($i, $a, $c, $d, $n, $f) if (!defined ($cl_hash) ||
  1844. ($cl_hash->{TYPE} ne 'HMCCUDEV' && $cl_hash->{TYPE} ne 'HMCCUCHN'));
  1845. $object = $cl_hash->{ccuaddr};
  1846. $object .= ":$fhcdp" if (defined ($fhcdp));
  1847. }
  1848. # Check if address is already known by HMCCU. Substitute device address by ZZZ0000000
  1849. # to allow external addresses like HVL
  1850. if ($object =~ /^.+\.(.+):[0-9]{1,2}\..+$/ ||
  1851. $object =~ /^.+\.(.+):[0-9]{1,2}$/ ||
  1852. $object =~ /^(.+):[0-9]{1,2}\..+$/ ||
  1853. $object =~ /^(.+):[0-9]{1,2}$/ ||
  1854. $object =~ /^(.+)$/) {
  1855. $extaddr = $1;
  1856. if (!HMCCU_IsDevAddr ($extaddr, 0) &&
  1857. exists ($hash->{hmccu}{dev}{$extaddr}) && $hash->{hmccu}{dev}{$extaddr}{valid}) {
  1858. $object =~ s/$extaddr/$HMCCU_EXT_ADDR/;
  1859. }
  1860. }
  1861. if ($object =~ /^(.+?)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})\.(.+)$/ ||
  1862. $object =~ /^(.+?)\.([0-9A-F]{12,14}):([0-9]{1,2})\.(.+)$/ ||
  1863. $object =~ /^(.+?)\.(OL-.+):([0-9]{1,2})\.(.+)$/ ||
  1864. $object =~ /^(.+?)\.(BidCoS-RF):([0-9]{1,2})\.(.+)$/) {
  1865. #
  1866. # Interface.Address:Channel.Datapoint [30=11110]
  1867. #
  1868. $f = $HMCCU_FLAGS_IACD;
  1869. ($i, $a, $c, $d) = ($1, $2, $3, $4);
  1870. }
  1871. elsif ($object =~ /^(.+)\.([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ ||
  1872. $object =~ /^(.+)\.([0-9A-F]{12,14}):([0-9]{1,2})$/ ||
  1873. $object =~ /^(.+)\.(OL-.+):([0-9]{1,2})$/ ||
  1874. $object =~ /^(.+)\.(BidCoS-RF):([0-9]{1,2})$/) {
  1875. #
  1876. # Interface.Address:Channel [26=11010]
  1877. #
  1878. $f = $HMCCU_FLAGS_IAC | ($flags & $HMCCU_FLAG_DATAPOINT);
  1879. ($i, $a, $c, $d) = ($1, $2, $3, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1880. }
  1881. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})\.(.+)$/ ||
  1882. $object =~ /^([0-9A-F]{12,14}):([0-9]{1,2})\.(.+)$/ ||
  1883. $object =~ /^(OL-.+):([0-9]{1,2})\.(.+)$/ ||
  1884. $object =~ /^(BidCoS-RF):([0-9]{1,2})\.(.+)$/) {
  1885. #
  1886. # Address:Channel.Datapoint [14=01110]
  1887. #
  1888. $f = $HMCCU_FLAGS_ACD;
  1889. ($a, $c, $d) = ($1, $2, $3);
  1890. }
  1891. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7}):([0-9]{1,2})$/ ||
  1892. $object =~ /^([0-9A-Z]{12,14}):([0-9]{1,2})$/ ||
  1893. $object =~ /^(OL-.+):([0-9]{1,2})$/ ||
  1894. $object =~ /^(BidCoS-RF):([0-9]{1,2})$/) {
  1895. #
  1896. # Address:Channel [10=01010]
  1897. #
  1898. $f = $HMCCU_FLAGS_AC | ($flags & $HMCCU_FLAG_DATAPOINT);
  1899. ($a, $c, $d) = ($1, $2, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1900. }
  1901. elsif ($object =~ /^([\*]*[A-Z]{3}[0-9]{7})$/ ||
  1902. $object =~ /^([0-9A-Z]{12,14})$/ ||
  1903. $object =~ /^(OL-.+)$/ ||
  1904. $object eq 'BidCoS') {
  1905. #
  1906. # Address
  1907. #
  1908. $f = $HMCCU_FLAG_ADDRESS;
  1909. $a = $1;
  1910. }
  1911. elsif ($object =~ /^(.+?)\.([A-Z_]+)$/) {
  1912. #
  1913. # Name.Datapoint
  1914. #
  1915. $f = $HMCCU_FLAGS_ND;
  1916. ($n, $d) = ($1, $2);
  1917. }
  1918. elsif ($object =~ /^.+$/) {
  1919. #
  1920. # Name [1=00001]
  1921. #
  1922. $f = $HMCCU_FLAG_NAME | ($flags & $HMCCU_FLAG_DATAPOINT);
  1923. ($n, $d) = ($object, $flags & $HMCCU_FLAG_DATAPOINT ? '.*' : '');
  1924. }
  1925. else {
  1926. $f = 0;
  1927. }
  1928. # Restore external address (i.e. HVL device address)
  1929. $a = $extaddr if ($a eq $HMCCU_EXT_ADDR);
  1930. # Check if name is a valid channel name
  1931. if ($f & $HMCCU_FLAG_NAME) {
  1932. my ($add, $chn) = HMCCU_GetAddress ($hash, $n, '', '');
  1933. if ($chn ne '') {
  1934. $f = $f | $HMCCU_FLAG_CHANNEL;
  1935. }
  1936. if ($flags & $HMCCU_FLAG_FULLADDR) {
  1937. ($i, $a, $c) = (HMCCU_GetDeviceInterface ($hash, $add, 'BidCos-RF'), $add, $chn);
  1938. $f |= $HMCCU_FLAG_INTERFACE;
  1939. $f |= $HMCCU_FLAG_ADDRESS if ($add ne '');
  1940. $f |= $HMCCU_FLAG_CHANNEL if ($chn ne '');
  1941. }
  1942. }
  1943. elsif ($f & $HMCCU_FLAG_ADDRESS && $i eq '' &&
  1944. ($flags & $HMCCU_FLAG_FULLADDR || $flags & $HMCCU_FLAG_INTERFACE)) {
  1945. $i = HMCCU_GetDeviceInterface ($hash, $a, 'BidCos-RF');
  1946. $f |= $HMCCU_FLAG_INTERFACE;
  1947. }
  1948. return ($i, $a, $c, $d, $n, $f);
  1949. }
  1950. ######################################################################
  1951. # Filter reading by datapoint and optionally by channel name or
  1952. # channel address.
  1953. # Parameter channel can be a channel name or a channel address without
  1954. # interface specification.
  1955. # Filter rule syntax is either:
  1956. # [N:]{Channel-Number|Channel-Name-Expr}!Datapoint-Expr
  1957. # or
  1958. # [N:][Channel-Number.]Datapoint-Expr
  1959. # Multiple filter rules must be separated by ;
  1960. ######################################################################
  1961. sub HMCCU_FilterReading ($$$)
  1962. {
  1963. my ($hash, $chn, $dpt) = @_;
  1964. my $name = $hash->{NAME};
  1965. my $fnc = "FilterReading";
  1966. my $hmccu_hash = HMCCU_GetHash ($hash);
  1967. return 1 if (!defined ($hmccu_hash));
  1968. my $grf = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingfilter', '.*');
  1969. # $grf = '.*' if ($grf eq '');
  1970. my $rf = AttrVal ($name, 'ccureadingfilter', $grf);
  1971. # $rf = $grf.";".$rf if ($rf ne $grf && $grf ne '.*' && $grf ne '');
  1972. $rf = $grf.";".$rf if ($rf ne $grf && $grf ne '.*');
  1973. my $chnnam = '';
  1974. my $chnnum = '';
  1975. my $devadd = '';
  1976. # Get channel name and channel number
  1977. if (HMCCU_IsValidChannel ($hmccu_hash, $chn, $HMCCU_FL_ADDRESS)) {
  1978. $chnnam = HMCCU_GetChannelName ($hmccu_hash, $chn, '');
  1979. ($devadd, $chnnum) = HMCCU_SplitChnAddr ($chn);
  1980. }
  1981. else {
  1982. ($devadd, $chnnum) = HMCCU_GetAddress ($hash, $chn, '', '');
  1983. $chnnam = $chn;
  1984. }
  1985. HMCCU_Trace ($hash, 2, $fnc, "chn=$chn, chnnam=$chnnam chnnum=$chnnum dpt=$dpt, rules=$rf");
  1986. foreach my $r (split (';', $rf)) {
  1987. my $rm = 1;
  1988. my $cn = '';
  1989. # Negative filter
  1990. if ($r =~ /^N:/) {
  1991. $rm = 0;
  1992. $r =~ s/^N://;
  1993. }
  1994. # Get filter criteria
  1995. my ($c, $f) = split ("!", $r);
  1996. if (defined ($f)) {
  1997. next if ($c eq '' || $chnnam eq '' || $chnnum eq '');
  1998. $cn = $c if ($c =~ /^([0-9]{1,2})$/);
  1999. }
  2000. else {
  2001. $c = '';
  2002. if ($r =~ /^([0-9]{1,2})\.(.+)$/) {
  2003. $cn = $1;
  2004. $f = $2;
  2005. }
  2006. else {
  2007. $cn = '';
  2008. $f = $r;
  2009. }
  2010. }
  2011. HMCCU_Trace ($hash, 2, undef, " check rm=$rm f=$f cn=$cn c=$c");
  2012. # Positive filter
  2013. return 1 if (
  2014. $rm && (
  2015. (
  2016. ($cn ne '' && "$chnnum" eq "$cn") ||
  2017. ($c ne '' && $chnnam =~ /$c/) ||
  2018. ($cn eq '' && $c eq '')
  2019. ) && $dpt =~ /$f/
  2020. )
  2021. );
  2022. # Negate filter
  2023. return 1 if (
  2024. !$rm && (
  2025. ($cn ne '' && "$chnnum" ne "$cn") ||
  2026. ($c ne '' && $chnnam !~ /$c/) ||
  2027. $dpt !~ /$f/
  2028. )
  2029. );
  2030. HMCCU_Trace ($hash, 2, undef, " check result false");
  2031. }
  2032. return 0;
  2033. }
  2034. ######################################################################
  2035. # Build reading name
  2036. #
  2037. # Parameters:
  2038. #
  2039. # Interface,Address,ChannelNo,Datapoint,ChannelNam,ReadingFormat
  2040. # ReadingFormat := { name[lc] | datapoint[lc] | address[lc] }
  2041. #
  2042. # Valid combinations:
  2043. #
  2044. # ChannelName,Datapoint
  2045. # Address,Datapoint
  2046. # Address,ChannelNo,Datapoint
  2047. #
  2048. # Reading names can be modified by setting attribut ccureadingname.
  2049. # Returns list of readings names.
  2050. ######################################################################
  2051. sub HMCCU_GetReadingName ($$$$$$$)
  2052. {
  2053. my ($hash, $i, $a, $c, $d, $n, $rf) = @_;
  2054. my $name = $hash->{NAME};
  2055. my $hmccu_hash = HMCCU_GetHash ($hash);
  2056. return '' if (!defined ($hmccu_hash));
  2057. my $rn = '';
  2058. my @rnlist;
  2059. Log3 $name, 1, "HMCCU: ChannelNo undefined: Addr=".$a if (!defined ($c));
  2060. $rf = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash) if (!defined ($rf));
  2061. my $gsr = AttrVal ($hmccu_hash->{NAME}, 'ccudef-readingname', '');
  2062. my $sr = AttrVal ($name, 'ccureadingname', $gsr);
  2063. $sr .= ";".$gsr if ($sr ne $gsr && $gsr ne '');
  2064. # Datapoint is mandatory
  2065. return '' if ($d eq '');
  2066. if ($rf eq 'datapoint' || $rf eq 'datapointlc') {
  2067. $rn = (defined ($c) && $c ne '') ? $c.'.'.$d : $d;
  2068. }
  2069. elsif ($rf eq 'name' || $rf eq 'namelc') {
  2070. if ($n eq '') {
  2071. if ($a ne '' && $c ne '') { $n = HMCCU_GetChannelName ($hmccu_hash, $a.':'.$c, ''); }
  2072. elsif ($a ne '' && $c eq '') { $n = HMCCU_GetDeviceName ($hmccu_hash, $a, ''); }
  2073. else { return ''; }
  2074. }
  2075. # Substitue unsupported characters in reading name
  2076. $n = HMCCU_CorrectName ($n);
  2077. return '' if ($n eq '');
  2078. $rn = $n.'.'.$d;
  2079. }
  2080. elsif ($rf eq 'address' || $rf eq 'addresslc') {
  2081. if ($a eq '' && $n ne '') {
  2082. ($a, $c) = HMCCU_GetAddress ($hmccu_hash, $n, '', '');
  2083. }
  2084. if ($a ne '') {
  2085. my $t = $a;
  2086. $i = HMCCU_GetDeviceInterface ($hmccu_hash, $a, '') if ($i eq '');
  2087. $t = $i.'.'.$t if ($i ne '');
  2088. $t = $t.'.'.$c if ($c ne '');
  2089. $rn = $t.'.'.$d;
  2090. }
  2091. }
  2092. push (@rnlist, $rn);
  2093. # Rename and/or add reading names
  2094. if ($sr ne '') {
  2095. my @rules = split (';', $sr);
  2096. foreach my $rr (@rules) {
  2097. my ($rold, $rnew) = split (':', $rr);
  2098. next if (!defined ($rnew));
  2099. if ($rnlist[0] =~ /$rold/) {
  2100. if ($rnew =~ /^\+(.+)$/) {
  2101. my $radd = $1;
  2102. $radd =~ s/$rold/$radd/;
  2103. push (@rnlist, $radd);
  2104. }
  2105. else {
  2106. $rnlist[0] =~ s/$rold/$rnew/;
  2107. }
  2108. }
  2109. }
  2110. }
  2111. # Convert to lowercase
  2112. $rnlist[0] = lc($rnlist[0]) if ($rf =~ /lc$/);
  2113. return @rnlist;
  2114. }
  2115. ######################################################################
  2116. # Format reading value depending on attribute stripnumber.
  2117. # Syntax of attribute stripnumber:
  2118. # [datapoint-expr!]format[;...]
  2119. # Valid formats:
  2120. # 0 = Remove all digits
  2121. # 1 = Preserve 1 digit
  2122. # 2 = Remove trailing zeroes
  2123. # -n = Round value to specified number of digits (-0 is allowed)
  2124. # %f = Format for numbers. String suffix is allowed.
  2125. ######################################################################
  2126. sub HMCCU_FormatReadingValue ($$$)
  2127. {
  2128. my ($hash, $value, $dpt) = @_;
  2129. my $stripnumber = AttrVal ($hash->{NAME}, 'stripnumber', 'null');
  2130. return $value if ($stripnumber eq 'null' || $value !~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/);
  2131. my $isint = $value =~ /^[+-]?[0-9]+$/ ? 1 : 0;
  2132. foreach my $sr (split (';', $stripnumber)) {
  2133. my ($d, $s) = split ('!', $sr);
  2134. if (defined ($s)) {
  2135. next if ($d eq '' || $dpt !~ /$d/);
  2136. }
  2137. else {
  2138. $s = $sr;
  2139. }
  2140. if ($s eq '0' && !$isint) { return sprintf ("%d", $value); }
  2141. elsif ($s eq '1' && !$isint) { return sprintf ("%.1f", $value); }
  2142. elsif ($s eq '2' && !$isint) { return sprintf ("%g", $value); }
  2143. elsif ($s =~ /^-([0-9])$/ && !$isint) { my $f = '%.'.$1.'f'; return sprintf ($f, $value); }
  2144. elsif ($s =~ /^%.+$/) { return sprintf ($s, $value); }
  2145. }
  2146. return $value;
  2147. }
  2148. ######################################################################
  2149. # Log message if trace flag is set.
  2150. # Will output multiple log file entries if parameter msg is separated
  2151. # by <br>
  2152. ######################################################################
  2153. sub HMCCU_Trace ($$$$)
  2154. {
  2155. my ($hash, $level, $fnc, $msg) = @_;
  2156. my $name = $hash->{NAME};
  2157. my $type = $hash->{TYPE};
  2158. return if (!HMCCU_IsFlag ($name, "trace"));
  2159. foreach my $m (split ("<br>", $msg)) {
  2160. $m = "$fnc: $m" if (defined ($fnc) && $fnc ne '');
  2161. Log3 $name, $level, "$type: $m";
  2162. }
  2163. }
  2164. ######################################################################
  2165. # Log message and return parameter rc.
  2166. ######################################################################
  2167. sub HMCCU_Log ($$$$)
  2168. {
  2169. my ($hash, $level, $msg, $rc) = @_;
  2170. my $name = "n/a";
  2171. my $type = "n/a";
  2172. $name = $hash->{NAME} if (exists ($hash->{NAME}));
  2173. $type = $hash->{TYPE} if (exists ($hash->{TYPE}));
  2174. Log3 $name, $level, "$type: [$name] $msg";
  2175. return $rc;
  2176. }
  2177. ######################################################################
  2178. # Log message and return message preceded by string "ERROR: ".
  2179. ######################################################################
  2180. sub HMCCU_LogError ($$$)
  2181. {
  2182. my ($hash, $level, $msg) = @_;
  2183. HMCCU_Log ($hash, $level, $msg, undef);
  2184. return "ERROR: $msg";
  2185. }
  2186. ######################################################################
  2187. # Set error state and write log file message
  2188. # Parameter text can be an error code (integer < 0) or an error text.
  2189. # Parameter addinfo is optional.
  2190. # Return error message.
  2191. ######################################################################
  2192. sub HMCCU_SetError ($@)
  2193. {
  2194. my ($hash, $text, $addinfo) = @_;
  2195. my $name = $hash->{NAME};
  2196. my $type = $hash->{TYPE};
  2197. my $msg;
  2198. my %errlist = (
  2199. -1 => 'Invalid device/channel name or address',
  2200. -2 => 'Execution of CCU script or command failed',
  2201. -3 => 'Cannot detect IO device',
  2202. -4 => 'Device deleted in CCU',
  2203. -5 => 'No response from CCU',
  2204. -6 => 'Update of readings disabled. Set attribute ccureadings first',
  2205. -7 => 'Invalid channel number',
  2206. -8 => 'Invalid datapoint',
  2207. -9 => 'Interface does not support RPC calls',
  2208. -10 => 'No readable datapoints found',
  2209. -11 => 'No state channel defined',
  2210. -12 => 'No control channel defined',
  2211. -13 => 'No state datapoint defined',
  2212. -14 => 'No control datapoint defined',
  2213. -15 => 'No state values defined',
  2214. -16 => 'Cannot open file',
  2215. -17 => 'Cannot detect or create external RPC device',
  2216. -18 => 'Type of system variable not supported',
  2217. -19 => 'Device not initialized',
  2218. -20 => 'Invalid or unknown device interface'
  2219. );
  2220. $msg = exists ($errlist{$text}) ? $errlist{$text} : $text;
  2221. $msg = $type.": ".$name." ". $msg;
  2222. if (defined ($addinfo) && $addinfo ne '') {
  2223. $msg .= ". $addinfo";
  2224. }
  2225. HMCCU_Log ($hash, 1, $msg, undef);
  2226. return HMCCU_SetState ($hash, "Error", $msg);
  2227. }
  2228. ######################################################################
  2229. # Set state of device if attribute ccuflags = ackState
  2230. ######################################################################
  2231. sub HMCCU_SetState ($@)
  2232. {
  2233. my ($hash, $text, $retval) = @_;
  2234. my $name = $hash->{NAME};
  2235. my $ccuflags = HMCCU_GetFlags ($name);
  2236. $ccuflags .= ',ackState' if ($hash->{TYPE} eq 'HMCCU' && $ccuflags !~ /ackState/);
  2237. if (defined ($hash) && defined ($text) && $ccuflags =~ /ackState/) {
  2238. readingsSingleUpdate ($hash, 'state', $text, 1)
  2239. if (ReadingsVal ($name, 'state', '') ne $text);
  2240. }
  2241. return $retval;
  2242. }
  2243. ######################################################################
  2244. # Set state of RPC server. Update all client devices if overall state
  2245. # is 'running'.
  2246. # Parameters iface and msg are optional. If iface is set function
  2247. # was called by HMCCURPCPROC device.
  2248. ######################################################################
  2249. sub HMCCU_SetRPCState ($@)
  2250. {
  2251. my ($hash, $state, $iface, $msg) = @_;
  2252. my $name = $hash->{NAME};
  2253. my $ccuflags = HMCCU_GetFlags ($name);
  2254. my $filter;
  2255. my $rpcstate = $state;
  2256. if ($ccuflags =~ /intrpc/ || $ccuflags !~ /(intrpc|extrpc|procrpc)/) {
  2257. if ($state ne $hash->{RPCState}) {
  2258. $hash->{RPCState} = $state;
  2259. readingsSingleUpdate ($hash, "rpcstate", $state, 1);
  2260. HMCCU_Log ($hash, 4, "Set rpcstate to $state", undef);
  2261. HMCCU_Log ($hash, 1, $msg, undef) if (defined ($msg));
  2262. HMCCU_Log ($hash, 1, "All RPC servers $state", undef);
  2263. DoTrigger ($name, "RPC server $state");
  2264. if ($state eq 'running') {
  2265. my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, '.*', 'Attr', 0, $filter);
  2266. HMCCU_Log ($hash, 2, "Updated devices. Success=$c_ok Failed=$c_err", undef);
  2267. }
  2268. }
  2269. }
  2270. elsif (defined ($iface) && $ccuflags =~ /(procrpc|extrpc)/) {
  2271. # Set interface state
  2272. my $ifname = HMCCU_GetRPCServerInfo ($hash, $iface, 'name');
  2273. $hash->{hmccu}{interfaces}{$ifname}{state} = $state if (defined ($ifname));
  2274. # Count number of processes in state running, error or inactive
  2275. # Prepare filter for updating client devices
  2276. my %stc = ("running" => 0, "error" => 0, "inactive" => 0);
  2277. my @iflist = HMCCU_GetRPCInterfaceList ($hash);
  2278. foreach my $i (@iflist) {
  2279. my $st = $hash->{hmccu}{interfaces}{$i}{state};
  2280. $stc{$st}++ if (exists ($stc{$st}));
  2281. if ($hash->{hmccu}{interfaces}{$i}{manager} eq 'HMCCU') {
  2282. $filter = defined ($filter) ? "$filter|$i" : $i;
  2283. }
  2284. }
  2285. # Determine overall process state
  2286. my $rpcstate = 'null';
  2287. $rpcstate = "running" if ($stc{"running"} == scalar (@iflist));
  2288. $rpcstate = "inactive" if ($stc{"inactive"} == scalar (@iflist));
  2289. $rpcstate = "error" if ($stc{"error"} == scalar (@iflist));
  2290. if ($rpcstate =~ /^(running|inactive|error)$/) {
  2291. if ($rpcstate ne $hash->{RPCState}) {
  2292. $hash->{RPCState} = $rpcstate;
  2293. readingsSingleUpdate ($hash, "rpcstate", $rpcstate, 1);
  2294. HMCCU_Log ($hash, 4, "Set rpcstate to $rpcstate", undef);
  2295. HMCCU_Log ($hash, 1, $msg, undef) if (defined ($msg));
  2296. HMCCU_Log ($hash, 1, "All RPC servers $rpcstate", undef);
  2297. DoTrigger ($name, "RPC server $rpcstate");
  2298. if ($rpcstate eq 'running') {
  2299. my ($c_ok, $c_err) = HMCCU_UpdateClients ($hash, '.*', 'Attr', 0, $filter);
  2300. HMCCU_Log ($hash, 2, "Updated devices. Success=$c_ok Failed=$c_err", undef);
  2301. }
  2302. }
  2303. }
  2304. }
  2305. # Set I/O device state
  2306. if ($rpcstate eq 'running' || $rpcstate eq 'inactive') {
  2307. HMCCU_SetState ($hash, "OK");
  2308. }
  2309. elsif ($rpcstate eq 'error') {
  2310. HMCCU_SetState ($hash, "error");
  2311. }
  2312. else {
  2313. HMCCU_SetState ($hash, "busy");
  2314. }
  2315. return undef;
  2316. }
  2317. ######################################################################
  2318. # Substitute first occurrence of regular expression or fixed string.
  2319. # Floating point values are ignored without datapoint specification.
  2320. # Integer values are compared with complete value.
  2321. # mode: 0=Substitute regular expression, 1=Substitute text
  2322. ######################################################################
  2323. sub HMCCU_Substitute ($$$$$)
  2324. {
  2325. my ($value, $substrule, $mode, $chn, $dpt) = @_;
  2326. my $rc = 0;
  2327. my $newvalue;
  2328. return $value if (!defined ($substrule) || $substrule eq '');
  2329. # Remove channel number from datapoint if specified
  2330. if ($dpt =~ /^([0-9]{1,2})\.(.+)$/) {
  2331. ($chn, $dpt) = ($1, $2);
  2332. }
  2333. my @rulelist = split (';', $substrule);
  2334. foreach my $rule (@rulelist) {
  2335. my @ruletoks = split ('!', $rule);
  2336. if (@ruletoks == 2 && $dpt ne '' && $mode == 0) {
  2337. my @dptlist = split (',', $ruletoks[0]);
  2338. foreach my $d (@dptlist) {
  2339. my $c = -1;
  2340. if ($d =~ /^([0-9]{1,2})\.(.+)$/) {
  2341. ($c, $d) = ($1, $2);
  2342. }
  2343. if ($d eq $dpt && ($c == -1 || !defined($chn) || $c == $chn)) {
  2344. ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[1], $mode);
  2345. return $newvalue;
  2346. }
  2347. }
  2348. }
  2349. elsif (@ruletoks == 1) {
  2350. return $value if ($value !~ /^[+-]?\d+$/ && $value =~ /^[+-]?\d*\.?\d+(?:(?:e|E)\d+)?$/);
  2351. ($rc, $newvalue) = HMCCU_SubstRule ($value, $ruletoks[0], $mode);
  2352. return $newvalue if ($rc == 1);
  2353. }
  2354. }
  2355. return $value;
  2356. }
  2357. ######################################################################
  2358. # Execute substitution list.
  2359. # Syntax for single substitution: {#n-n|regexp|text}:newtext
  2360. # mode=0: Substitute regular expression
  2361. # mode=1: Substitute text (for setting statevals)
  2362. # newtext can contain ':'. Parameter ${value} in newtext is
  2363. # substituted by original value.
  2364. # Return (status, value)
  2365. # status=1: value = substituted value
  2366. # status=0: value = original value
  2367. ######################################################################
  2368. sub HMCCU_SubstRule ($$$)
  2369. {
  2370. my ($value, $substitutes, $mode ) = @_;
  2371. my $rc = 0;
  2372. $substitutes =~ s/\$\{value\}/$value/g;
  2373. my @sub_list = split /,/,$substitutes;
  2374. foreach my $s (@sub_list) {
  2375. my ($regexp, $text) = split /:/,$s,2;
  2376. next if (!defined ($regexp) || !defined($text));
  2377. if ($regexp =~ /^#([+-]?\d*\.?\d+?)\-([+-]?\d*\.?\d+?)$/) {
  2378. my ($mi, $ma) = ($1, $2);
  2379. if ($value =~ /^\d*\.?\d+?$/ && $value >= $mi && $value <= $ma) {
  2380. $value = $text;
  2381. $rc = 1;
  2382. }
  2383. }
  2384. elsif ($mode == 0 && $value =~ /$regexp/ && $value !~ /^[+-]?\d+$/) {
  2385. my $x = eval { $value =~ s/$regexp/$text/ };
  2386. $rc = 1 if (defined ($x));
  2387. last;
  2388. }
  2389. elsif (($mode == 1 || $value =~ /^[+-]?\d+$/) && $value =~ /^$regexp$/) {
  2390. my $x = eval { $value =~ s/^$regexp$/$text/ };
  2391. $rc = 1 if (defined ($x));
  2392. last;
  2393. }
  2394. }
  2395. return ($rc, $value);
  2396. }
  2397. ######################################################################
  2398. # Substitute datapoint variables in string by datapoint value. The
  2399. # value depends on the character preceding the variable name. Syntax
  2400. # of variable names is:
  2401. # {$|$$|%|%%}{[cn.]Name}
  2402. # {$|$$|%|%%}[cn.]Name
  2403. # % = Original / raw value
  2404. # %% = Previous original / raw value
  2405. # $ = Converted / formatted value
  2406. # $$ = Previous converted / formatted value
  2407. # Parameter dplist is a comma separated list of value keys in format
  2408. # [address:]Channel.Datapoint.
  2409. ######################################################################
  2410. sub HMCCU_SubstVariables ($$$)
  2411. {
  2412. my ($clhash, $text, $dplist) = @_;
  2413. my $fnc = "HMCCU_SubstVariables";
  2414. my @varlist;
  2415. if (defined ($dplist)) {
  2416. @varlist = split (',', $dplist);
  2417. }
  2418. else {
  2419. @varlist = keys %{$clhash->{hmccu}{dp}};
  2420. }
  2421. HMCCU_Trace ($clhash, 2, $fnc, "text=$text");
  2422. # Substitute datapoint variables by value
  2423. foreach my $dp (@varlist) {
  2424. my ($chn, $dpt) = split (/\./, $dp);
  2425. HMCCU_Trace ($clhash, 2, $fnc, "var=$dp");
  2426. if (defined ($clhash->{hmccu}{dp}{$dp}{OSVAL})) {
  2427. $text =~ s/\$\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g;
  2428. $text =~ s/\$\$\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{OSVAL}/g;
  2429. }
  2430. if (defined ($clhash->{hmccu}{dp}{$dp}{SVAL})) {
  2431. $text =~ s/\$\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{SVAL}/g;
  2432. $text =~ s/\$\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{SVAL}/g;
  2433. }
  2434. if (defined ($clhash->{hmccu}{dp}{$dp}{OVAL})) {
  2435. $text =~ s/\%\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{OVAL}/g;
  2436. $text =~ s/\%\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{OVAL}/g;
  2437. }
  2438. if (defined ($clhash->{hmccu}{dp}{$dp}{VAL})) {
  2439. $text =~ s/\%\{?$dp\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g;
  2440. $text =~ s/\%\{?$dpt\}?/$clhash->{hmccu}{dp}{$dp}{VAL}/g;
  2441. $text =~ s/$dp/$clhash->{hmccu}{dp}{$dp}{VAL}/g;
  2442. }
  2443. }
  2444. HMCCU_Trace ($clhash, 2, $fnc, "text=$text");
  2445. return $text;
  2446. }
  2447. ######################################################################
  2448. # Update all datapoint/readings of all client devices matching
  2449. # specified regular expression. Update will fail if device is deleted
  2450. # or disabled or if attribute ccureadings of a device is set to 0.
  2451. # If fromccu is 1 regular expression is compared to CCU device name.
  2452. # Otherwise it's compared to FHEM device name. If ifname is specified
  2453. # only devices belonging to interface ifname are updated.
  2454. ######################################################################
  2455. sub HMCCU_UpdateClients ($$$$$)
  2456. {
  2457. my ($hash, $devexp, $ccuget, $fromccu, $ifname) = @_;
  2458. my $fhname = $hash->{NAME};
  2459. my $c_ok = 0;
  2460. my $c_err = 0;
  2461. my $filter = "ccudevstate=active";
  2462. $filter .= ",ccuif=$ifname" if (defined ($ifname));
  2463. if ($fromccu) {
  2464. foreach my $name (sort keys %{$hash->{hmccu}{adr}}) {
  2465. next if ($name !~ /$devexp/ || !($hash->{hmccu}{adr}{$name}{valid}));
  2466. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, $filter);
  2467. foreach my $d (@devlist) {
  2468. my $ch = $defs{$d};
  2469. next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
  2470. next if ($ch->{ccuaddr} ne $hash->{hmccu}{adr}{$name}{address});
  2471. my $rc = HMCCU_GetUpdate ($ch, $hash->{hmccu}{adr}{$name}{address}, $ccuget);
  2472. if ($rc <= 0) {
  2473. if ($rc == -10) {
  2474. Log3 $fhname, 3, "HMCCU: Device $name has no readable datapoints";
  2475. }
  2476. else {
  2477. Log3 $fhname, 2, "HMCCU: Update of device $name failed";
  2478. }
  2479. $c_err++;
  2480. }
  2481. else {
  2482. $c_ok++;
  2483. }
  2484. }
  2485. }
  2486. }
  2487. else {
  2488. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", $devexp, $filter);
  2489. Log3 $fhname, 2, "HMCCU: No client devices matching $devexp" if (scalar (@devlist) == 0);
  2490. foreach my $d (@devlist) {
  2491. my $ch = $defs{$d};
  2492. next if (!defined ($ch->{IODev}) || !defined ($ch->{ccuaddr}));
  2493. my $rc = HMCCU_GetUpdate ($ch, $ch->{ccuaddr}, $ccuget);
  2494. if ($rc <= 0) {
  2495. if ($rc == -10) {
  2496. Log3 $fhname, 3, "HMCCU: Device ".$ch->{ccuaddr}." has no readable datapoints";
  2497. }
  2498. else {
  2499. Log3 $fhname, 2, "HMCCU: Update of device ".$ch->{ccuaddr}." failed"
  2500. if ($ch->{ccuif} ne 'VirtualDevices' && $ch->{ccuif} ne 'fhem');
  2501. }
  2502. $c_err++;
  2503. }
  2504. else {
  2505. $c_ok++;
  2506. }
  2507. }
  2508. }
  2509. return ($c_ok, $c_err);
  2510. }
  2511. ##########################################################################
  2512. # Create virtual device in internal device tables.
  2513. # If sourceAddr is specified, parameter newType will be ignored.
  2514. # Return 0 on success or error code:
  2515. # 1 = newType not defined
  2516. # 2 = Device with newType not found in internal tables
  2517. ##########################################################################
  2518. sub HMCCU_CreateDevice ($$$$$)
  2519. {
  2520. my ($hash, $newAddr, $newName, $newType, $sourceAddr) = @_;
  2521. my %object;
  2522. if (!defined ($sourceAddr)) {
  2523. # Search for type in device table
  2524. return 1 if (!defined ($newType));
  2525. for my $da (keys %{$hash->{hmccu}{dev}}) {
  2526. if ($hash->{hmccu}{dev}{$da}{type} eq $newType) {
  2527. $sourceAddr = $da;
  2528. last;
  2529. }
  2530. }
  2531. return 2 if (!defined ($sourceAddr));
  2532. }
  2533. else {
  2534. $newType = $hash->{hmccu}{dev}{$sourceAddr}{type};
  2535. }
  2536. # Device attributes
  2537. $object{$newAddr}{flag} = 'N';
  2538. $object{$newAddr}{addtype} = 'dev';
  2539. $object{$newAddr}{channels} = $hash->{hmccu}{dev}{$sourceAddr}{channels};
  2540. $object{$newAddr}{name} = $newName;
  2541. $object{$newAddr}{type} = $newType;
  2542. $object{$newAddr}{interface} = 'fhem';
  2543. $object{$newAddr}{chndir} = 0;
  2544. # Channel attributes
  2545. for (my $chn=0; $chn<$object{$newAddr}{channels}; $chn++) {
  2546. my $ca = "$newAddr:$chn";
  2547. $object{$ca}{flag} = 'N';
  2548. $object{$ca}{addtype} = 'chn';
  2549. $object{$ca}{channels} = 1;
  2550. $object{$ca}{name} = "$newName:$chn";
  2551. $object{$ca}{chndir} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{chndir};
  2552. $object{$ca}{rxmode} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{rxmode};
  2553. $object{$ca}{usetype} = $hash->{hmccu}{dev}{"$sourceAddr:$chn"}{usetype};
  2554. }
  2555. HMCCU_UpdateDeviceTable ($hash, \%object);
  2556. return 0;
  2557. }
  2558. ##########################################################################
  2559. # Remove virtual device from internal device tables.
  2560. ##########################################################################
  2561. sub HMCCU_DeleteDevice ($)
  2562. {
  2563. my ($clthash) = @_;
  2564. my $name = $clthash->{NAME};
  2565. return if (!exists ($clthash->{IODev}) || !exists ($clthash->{ccuif}) ||
  2566. !exists ($clthash->{ccuaddr}));
  2567. return if ($clthash->{ccuif} ne 'fhem');
  2568. my $hmccu_hash = $clthash->{IODev};
  2569. my $devaddr = $clthash->{ccuaddr};
  2570. my $channels = exists ($clthash->{channels}) ? $clthash->{channels} : 0;
  2571. # Delete device address entries
  2572. if (exists ($hmccu_hash->{hmccu}{dev}{$devaddr})) {
  2573. delete $hmccu_hash->{hmccu}{dev}{$devaddr};
  2574. }
  2575. # Delete channel address and name entries
  2576. for (my $chn=0; $chn<=$channels; $chn++) {
  2577. if (exists ($hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"})) {
  2578. delete $hmccu_hash->{hmccu}{dev}{"$devaddr:$chn"};
  2579. }
  2580. if (exists ($hmccu_hash->{hmccu}{adr}{"$name:$chn"})) {
  2581. delete $hmccu_hash->{hmccu}{adr}{"$name:$chn"};
  2582. }
  2583. }
  2584. # Delete device name entries
  2585. if (exists ($hmccu_hash->{hmccu}{adr}{$name})) {
  2586. delete $hmccu_hash->{hmccu}{adr}{$name};
  2587. }
  2588. }
  2589. ##########################################################################
  2590. # Update parameters in internal device tables and client devices.
  2591. # Parameter devices is a hash reference with following keys:
  2592. # {address}
  2593. # {address}{flag} := [N, D, R] (N=New, D=Deleted, R=Renamed)
  2594. # {address}{addtype} := [chn, dev]
  2595. # {address}{channels} := Number of channels
  2596. # {address}{name} := Device or channel name
  2597. # {address}{type} := Homematic device type
  2598. # {address}{usetype} := Usage type
  2599. # {address}{interface} := Device interface ID
  2600. # {address}{firmware} := Firmware version of device
  2601. # {address}{version} := Version of RPC device description
  2602. # {address}{rxmode} := Transmit mode
  2603. # {address}{chndir} := Channel direction: 0=none, 1=sensor, 2=actor
  2604. # If flag is 'D' the hash must contain an entry for the device address
  2605. # and for each channel address.
  2606. ##########################################################################
  2607. sub HMCCU_UpdateDeviceTable ($$)
  2608. {
  2609. my ($hash, $devices) = @_;
  2610. my $name = $hash->{NAME};
  2611. my $devcount = 0;
  2612. my $chncount = 0;
  2613. # Update internal device table
  2614. foreach my $da (keys %{$devices}) {
  2615. my $nm = $hash->{hmccu}{dev}{$da}{name} if (defined ($hash->{hmccu}{dev}{$da}{name}));
  2616. $nm = $devices->{$da}{name} if (defined ($devices->{$da}{name}));
  2617. if ($devices->{$da}{flag} eq 'N' && defined ($nm)) {
  2618. my $at = '';
  2619. if (defined ($devices->{$da}{addtype})) {
  2620. $at = $devices->{$da}{addtype};
  2621. }
  2622. else {
  2623. $at = 'chn' if (HMCCU_IsChnAddr ($da, 0));
  2624. $at = 'dev' if (HMCCU_IsDevAddr ($da, 0));
  2625. }
  2626. if ($at eq '') {
  2627. Log3 $name, 2, "HMCCU: Cannot detect type of address $da";
  2628. next;
  2629. }
  2630. Log3 $name, 2, "HMCCU: Duplicate name for device/channel $nm address=$da in CCU."
  2631. if (exists ($hash->{hmccu}{adr}{$nm}) && $at ne $hash->{hmccu}{adr}{$nm}{addtype});
  2632. # Updated or new device/channel
  2633. $hash->{hmccu}{dev}{$da}{addtype} = $at;
  2634. $hash->{hmccu}{dev}{$da}{name} = $nm if (defined ($nm));
  2635. $hash->{hmccu}{dev}{$da}{valid} = 1;
  2636. $hash->{hmccu}{dev}{$da}{channels} = $devices->{$da}{channels}
  2637. if (defined ($devices->{$da}{channels}));
  2638. $hash->{hmccu}{dev}{$da}{type} = $devices->{$da}{type}
  2639. if (defined ($devices->{$da}{type}));
  2640. $hash->{hmccu}{dev}{$da}{usetype} = $devices->{$da}{usetype}
  2641. if (defined ($devices->{$da}{usetype}));
  2642. $hash->{hmccu}{dev}{$da}{interface} = $devices->{$da}{interface}
  2643. if (defined ($devices->{$da}{interface}));
  2644. $hash->{hmccu}{dev}{$da}{version} = $devices->{$da}{version}
  2645. if (defined ($devices->{$da}{version}));
  2646. $hash->{hmccu}{dev}{$da}{firmware} = $devices->{$da}{firmware}
  2647. if (defined ($devices->{$da}{firmware}));
  2648. $hash->{hmccu}{dev}{$da}{rxmode} = $devices->{$da}{rxmode}
  2649. if (defined ($devices->{$da}{rxmode}));
  2650. $hash->{hmccu}{dev}{$da}{chndir} = $devices->{$da}{chndir}
  2651. if (defined ($devices->{$da}{chndir}));
  2652. if (defined ($nm)) {
  2653. $hash->{hmccu}{adr}{$nm}{address} = $da;
  2654. $hash->{hmccu}{adr}{$nm}{addtype} = $hash->{hmccu}{dev}{$da}{addtype};
  2655. $hash->{hmccu}{adr}{$nm}{valid} = 1;
  2656. }
  2657. }
  2658. elsif ($devices->{$da}{flag} eq 'D' && exists ($hash->{hmccu}{dev}{$da})) {
  2659. # Device deleted, mark as invalid
  2660. $hash->{hmccu}{dev}{$da}{valid} = 0;
  2661. $hash->{hmccu}{adr}{$nm}{valid} = 0 if (defined ($nm));
  2662. }
  2663. elsif ($devices->{$da}{flag} eq 'R' && exists ($hash->{hmccu}{dev}{$da})) {
  2664. # Device replaced, change address
  2665. my $na = $devices->{hmccu}{newaddr};
  2666. # Copy device entries and delete old device entries
  2667. foreach my $k (keys %{$hash->{hmccu}{dev}{$da}}) {
  2668. $hash->{hmccu}{dev}{$na}{$k} = $hash->{hmccu}{dev}{$da}{$k};
  2669. }
  2670. $hash->{hmccu}{adr}{$nm}{address} = $na;
  2671. delete $hash->{hmccu}{dev}{$da};
  2672. }
  2673. }
  2674. # Delayed initialization if CCU was not ready during FHEM start
  2675. if ($hash->{hmccu}{ccu}{delayed} == 1) {
  2676. # Initialize interface and port lists
  2677. HMCCU_AttrInterfacesPorts ($hash, 'rpcinterfaces', $attr{$name}{rpcinterfaces})
  2678. if (exists ($attr{$name}{rpcinterfaces}));
  2679. HMCCU_AttrInterfacesPorts ($hash, 'rpcport', $attr{$name}{rpcport})
  2680. if (exists ($attr{$name}{rpcport}));
  2681. # Initialize pending client devices
  2682. my @cdev = HMCCU_FindClientDevices ($hash, '(HMCCUDEV|HMCCUCHN|HMCCURPCPROC)', undef, 'ccudevstate=pending');
  2683. if (scalar (@cdev) > 0) {
  2684. HMCCU_Log ($hash, 2, "Initializing ".scalar(@cdev)." client devices in state 'pending'", 0);
  2685. foreach my $cd (@cdev) {
  2686. my $ch = $defs{$cd};
  2687. my $ct = $ch->{TYPE};
  2688. my $rc = 0;
  2689. if ($ct eq 'HMCCUDEV') {
  2690. $rc = HMCCUDEV_InitDevice ($hash, $ch);
  2691. }
  2692. elsif ($ct eq 'HMCCUCHN') {
  2693. $rc = HMCCUCHN_InitDevice ($hash, $ch);
  2694. }
  2695. elsif ($ct eq 'HMCCURPCPROC') {
  2696. $rc = HMCCURPCPROC_InitDevice ($hash, $ch);
  2697. }
  2698. HMCCU_Log ($hash, 3, "Can't initialize client device ".$ch->{NAME}, 0) if ($rc > 0);
  2699. }
  2700. }
  2701. $hash->{hmccu}{ccu}{delayed} = 0;
  2702. }
  2703. # Update client devices
  2704. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef, undef);
  2705. foreach my $d (@devlist) {
  2706. my $ch = $defs{$d};
  2707. my $ct = $ch->{TYPE};
  2708. next if (!exists ($ch->{ccuaddr}));
  2709. my $ca = $ch->{ccuaddr};
  2710. next if (!exists ($devices->{$ca}));
  2711. if ($devices->{$ca}{flag} eq 'N') {
  2712. # New device or new device information
  2713. $ch->{ccudevstate} = 'active';
  2714. if ($ct eq 'HMCCUDEV') {
  2715. $ch->{ccutype} = $hash->{hmccu}{dev}{$ca}{type}
  2716. if (defined ($hash->{hmccu}{dev}{$ca}{type}));
  2717. $ch->{firmware} = $devices->{$ca}{firmware}
  2718. if (defined ($devices->{$ca}{firmware}));
  2719. }
  2720. else {
  2721. $ch->{chntype} = $devices->{$ca}{usetype}
  2722. if (defined ($devices->{$ca}{usetype}));
  2723. my ($add, $chn) = HMCCU_SplitChnAddr ($ca);
  2724. $ch->{ccutype} = $devices->{$add}{type}
  2725. if (defined ($devices->{$add}{type}));
  2726. $ch->{firmware} = $devices->{$add}{firmware}
  2727. if (defined ($devices->{$add}{firmware}));
  2728. }
  2729. $ch->{ccuname} = $hash->{hmccu}{dev}{$ca}{name}
  2730. if (defined ($hash->{hmccu}{dev}{$ca}{name}));
  2731. $ch->{ccuif} = $hash->{hmccu}{dev}{$ca}{interface}
  2732. if (defined ($devices->{$ca}{interface}));
  2733. $ch->{channels} = $hash->{hmccu}{dev}{$ca}{channels}
  2734. if (defined ($hash->{hmccu}{dev}{$ca}{channels}));
  2735. }
  2736. elsif ($devices->{$ca}{flag} eq 'D') {
  2737. # Deleted device
  2738. $ch->{ccudevstate} = 'deleted';
  2739. }
  2740. elsif ($devices->{$ca}{flag} eq 'R') {
  2741. # Replaced device
  2742. $ch->{ccuaddr} = $devices->{$ca}{newaddr};
  2743. }
  2744. }
  2745. # Update internals of I/O device
  2746. foreach my $adr (keys %{$hash->{hmccu}{dev}}) {
  2747. if (exists ($hash->{hmccu}{dev}{$adr}{addtype})) {
  2748. $devcount++ if ($hash->{hmccu}{dev}{$adr}{addtype} eq 'dev');
  2749. $chncount++ if ($hash->{hmccu}{dev}{$adr}{addtype} eq 'chn');
  2750. }
  2751. }
  2752. $hash->{ccudevices} = $devcount;
  2753. $hash->{ccuchannels} = $chncount;
  2754. return ($devcount, $chncount);
  2755. }
  2756. ######################################################################
  2757. # Update a single client device datapoint considering scaling, reading
  2758. # format and value substitution.
  2759. # Return stored value.
  2760. ######################################################################
  2761. sub HMCCU_UpdateSingleDatapoint ($$$$)
  2762. {
  2763. my ($hash, $chn, $dpt, $value) = @_;
  2764. my $hmccu_hash = HMCCU_GetHash ($hash);
  2765. return $value if (!defined ($hmccu_hash));
  2766. my %objects;
  2767. my $ccuaddr = $hash->{ccuaddr};
  2768. my ($devaddr, $chnnum) = HMCCU_SplitChnAddr ($ccuaddr);
  2769. $objects{$devaddr}{$chn}{$dpt} = $value;
  2770. # my $rc = HMCCU_UpdateMultipleDevices ($hmccu_hash, \%objects);
  2771. my $rc = HMCCU_UpdateSingleDevice ($hmccu_hash, $hash, \%objects, undef);
  2772. return (ref ($rc)) ? $rc->{$devaddr}{$chn}{$dpt} : $value;
  2773. }
  2774. ######################################################################
  2775. # Update readings of client device.
  2776. # Parameter objects is a hash reference which contains updated data
  2777. # for devices:
  2778. # {devaddr}{channelno}{datapoint} = value
  2779. # If client device is virtual device group: check if group members are
  2780. # affected by updates and update readings in virtual group device.
  2781. # Return a hash reference with datapoints and new values:
  2782. # {devaddr}{datapoint} = value
  2783. ######################################################################
  2784. sub HMCCU_UpdateSingleDevice ($$$$)
  2785. {
  2786. my ($ccuhash, $clthash, $objects, $alref) = @_;
  2787. my $ccuname = $ccuhash->{NAME};
  2788. my $cltname = $clthash->{NAME};
  2789. my $clttype = $clthash->{TYPE};
  2790. my $fnc = "UpdateSingleDevice";
  2791. return 0 if (!defined ($clthash->{IODev}) || !defined ($clthash->{ccuaddr}));
  2792. return 0 if ($clthash->{IODev} != $ccuhash);
  2793. # Build list of relevant addresses in object data hash
  2794. my ($devaddr, $cnum) = HMCCU_SplitChnAddr ($clthash->{ccuaddr});
  2795. my @addlist = defined ($alref) ? @$alref : ($devaddr);
  2796. # Check if update of device allowed
  2797. my $disable = AttrVal ($cltname, 'disable', 0);
  2798. my $update = AttrVal ($cltname, 'ccureadings', 1);
  2799. next if ($update == 0 || $disable == 1 || $clthash->{ccudevstate} ne 'active');
  2800. # Get device parameters and attributes
  2801. my $ccuflags = HMCCU_GetFlags ($ccuname);
  2802. my $cf = HMCCU_GetFlags ($cltname);
  2803. my $peer = AttrVal ($cltname, 'peer', 'null');
  2804. my $crf = HMCCU_GetAttrReadingFormat ($clthash, $ccuhash);
  2805. my $substitute = HMCCU_GetAttrSubstitute ($clthash, $ccuhash);
  2806. my ($sc, $st, $cc, $cd) = HMCCU_GetSpecialDatapoints ($clthash, '', 'STATE', '', '');
  2807. # Virtual device flag
  2808. my $vg = 0;
  2809. $vg = 1 if (($clthash->{ccuif} eq 'VirtualDevices' || $clthash->{ccuif} eq 'fhem') &&
  2810. exists ($clthash->{ccugroup}));
  2811. HMCCU_Trace ($clthash, 2, $fnc, "$cltname Objects = ".join(',', @addlist));
  2812. # Store the resulting readings
  2813. my %results;
  2814. # Updated internal values
  2815. my @chkeys = ();
  2816. # Update readings of client device with data from address list
  2817. readingsBeginUpdate ($clthash);
  2818. foreach my $addr (@addlist) {
  2819. next if (!exists ($objects->{$addr}));
  2820. HMCCU_Trace ($clthash, 2, $fnc, "Processing object $addr");
  2821. # Update channels of device
  2822. foreach my $chnnum (keys (%{$objects->{$addr}})) {
  2823. next if ($clttype eq 'HMCCUCHN' && "$chnnum" ne "$cnum" && "$chnnum" ne "0");
  2824. next if ("$chnnum" eq "0" && $cf =~ /nochn0/);
  2825. my $chnadd = "$addr:$chnnum";
  2826. # Update datapoints of channel
  2827. foreach my $dpt (keys (%{$objects->{$addr}{$chnnum}})) {
  2828. my $value = $objects->{$addr}{$chnnum}{$dpt};
  2829. next if (!defined ($value));
  2830. # Key for storing values in client hash. Indirect updates of virtual devices
  2831. # are stored with device address in key.
  2832. my $chkey = $devaddr ne $addr ? "$chnadd.$dpt" : "$chnnum.$dpt";
  2833. # Store datapoint raw value in device hash
  2834. HMCCU_UpdateInternalValues ($clthash, $chkey, 'VAL', $value);
  2835. HMCCU_Trace ($clthash, 2, $fnc, "dev=$cltname, chnadd/object=$chnadd, dpt=$dpt, key=$chkey, value=$value");
  2836. if (HMCCU_FilterReading ($clthash, $chnadd, $dpt)) {
  2837. # Modify reading name and value
  2838. my @readings = HMCCU_GetReadingName ($clthash, '', $addr, $chnnum, $dpt, '', $crf);
  2839. my $svalue = HMCCU_ScaleValue ($clthash, $chnnum, $dpt, $value, 0);
  2840. my $fvalue = HMCCU_FormatReadingValue ($clthash, $svalue, $dpt);
  2841. my $cvalue = HMCCU_Substitute ($fvalue, $substitute, 0, $chnnum, $dpt);
  2842. # my %calcs = HMCCU_CalculateReading ($clthash, $chkey);
  2843. # Store the resulting value after scaling, formatting and substitution
  2844. HMCCU_UpdateInternalValues ($clthash, $chkey, 'SVAL', $cvalue);
  2845. push @chkeys, $chkey;
  2846. # Store result, but not for indirect updates of virtual devices
  2847. $results{$devaddr}{$chnnum}{$dpt} = $cvalue if ($devaddr eq $addr);
  2848. HMCCU_Trace ($clthash, 2, $fnc,
  2849. "device=$cltname, readings=".join(',', @readings).
  2850. ", orgvalue=$value value=$cvalue peer=$peer");
  2851. # Update readings
  2852. foreach my $rn (@readings) {
  2853. HMCCU_BulkUpdate ($clthash, $rn, $fvalue, $cvalue) if ($rn ne '');
  2854. }
  2855. # foreach my $clcr (keys %calcs) {
  2856. # HMCCU_BulkUpdate ($clthash, $clcr, $calcs{$clcr}, $calcs{$clcr});
  2857. # }
  2858. HMCCU_BulkUpdate ($clthash, 'control', $fvalue, $cvalue)
  2859. if ($cd ne '' && $dpt eq $cd && $chnnum eq $cc);
  2860. HMCCU_BulkUpdate ($clthash, 'state', $fvalue, $cvalue)
  2861. if ($dpt eq $st && ($sc eq '' || $sc eq $chnnum));
  2862. # Update peers
  2863. HMCCU_UpdatePeers ($clthash, "$chnnum.$dpt", $cvalue, $peer) if (!$vg && $peer ne 'null');
  2864. }
  2865. }
  2866. }
  2867. }
  2868. if (scalar (@chkeys) > 0) {
  2869. my %calcs = HMCCU_CalculateReading ($clthash, \@chkeys);
  2870. foreach my $clcr (keys %calcs) {
  2871. HMCCU_BulkUpdate ($clthash, $clcr, $calcs{$clcr}, $calcs{$clcr});
  2872. }
  2873. }
  2874. # Calculate and update HomeMatic state
  2875. if ($ccuflags !~ /nohmstate/) {
  2876. my ($hms_read, $hms_chn, $hms_dpt, $hms_val) = HMCCU_GetHMState ($cltname, $ccuname, undef);
  2877. HMCCU_BulkUpdate ($clthash, $hms_read, $hms_val, $hms_val) if (defined ($hms_val));
  2878. }
  2879. readingsEndUpdate ($clthash, 1);
  2880. return \%results;
  2881. }
  2882. ######################################################################
  2883. # Store datapoint values in device hash.
  2884. # Parameter type is VAL or SVAL.
  2885. ######################################################################
  2886. sub HMCCU_UpdateInternalValues ($$$$)
  2887. {
  2888. my ($ch, $chkey, $type, $value) = @_;
  2889. my $otype = "O".$type;
  2890. if (exists ($ch->{hmccu}{dp}{$chkey}{$type})) {
  2891. $ch->{hmccu}{dp}{$chkey}{$otype} = $ch->{hmccu}{dp}{$chkey}{$type};
  2892. }
  2893. else {
  2894. $ch->{hmccu}{dp}{$chkey}{$otype} = $value;
  2895. }
  2896. $ch->{hmccu}{dp}{$chkey}{$type} = $value;
  2897. }
  2898. ######################################################################
  2899. # Update readings of multiple client devices.
  2900. # Parameter objects is a hash reference:
  2901. # {devaddr}
  2902. # {devaddr}{channelno}
  2903. # {devaddr}{channelno}{datapoint} = value
  2904. # Return number of updated devices.
  2905. ######################################################################
  2906. sub HMCCU_UpdateMultipleDevices ($$)
  2907. {
  2908. my ($hash, $objects) = @_;
  2909. my $name = $hash->{NAME};
  2910. my $fnc = "UpdateMultipleDevices";
  2911. my $c = 0;
  2912. # Check syntax
  2913. return 0 if (!defined ($hash) || !defined ($objects));
  2914. # Update reading in matching client devices
  2915. my @devlist = HMCCU_FindClientDevices ($hash, "(HMCCUDEV|HMCCUCHN)", undef,
  2916. "ccudevstate=active");
  2917. foreach my $d (@devlist) {
  2918. my $ch = $defs{$d};
  2919. my @addrlist = HMCCU_GetAffectedAddresses ($ch);
  2920. next if (scalar (@addrlist) == 0);
  2921. foreach my $addr (@addrlist) {
  2922. if (exists ($objects->{$addr})) {
  2923. my $rc = HMCCU_UpdateSingleDevice ($hash, $ch, $objects, \@addrlist);
  2924. $c++ if (ref ($rc));
  2925. last;
  2926. }
  2927. }
  2928. }
  2929. return $c;
  2930. }
  2931. ######################################################################
  2932. # Get list of device addresses including group device members.
  2933. ######################################################################
  2934. sub HMCCU_GetAffectedAddresses ($)
  2935. {
  2936. my ($clthash) = @_;
  2937. my @addlist = ();
  2938. if ($clthash->{TYPE} eq 'HMCCUDEV' || $clthash->{TYPE} eq 'HMCCUCHN') {
  2939. if (exists ($clthash->{ccuaddr})) {
  2940. my ($devaddr, $cnum) = HMCCU_SplitChnAddr ($clthash->{ccuaddr});
  2941. push @addlist, $devaddr;
  2942. }
  2943. if (($clthash->{ccuif} eq 'VirtualDevices' || $clthash->{ccuif} eq 'fhem') &&
  2944. exists ($clthash->{ccugroup})) {
  2945. push @addlist, split (',', $clthash->{ccugroup});
  2946. }
  2947. }
  2948. return @addlist;
  2949. }
  2950. ######################################################################
  2951. # Update peer devices.
  2952. # Syntax of peer definitions is:
  2953. # channel.datapoint[,...]:condition:type:action
  2954. # condition := valid perl expression. Any channel.datapoint
  2955. # combination is substituted by the corresponding value. If channel
  2956. # is preceded by a % it's substituted by the raw value. If it's
  2957. # preceded by a $ it's substituted by the formated/converted value.
  2958. # If % or $ is doubled the old values are used.
  2959. # type := type of action. Valid types are ccu, hmccu and fhem.
  2960. # action := Action to be performed if result of condition is true.
  2961. # Depending on type action type this could be an assignment or a
  2962. # FHEM command. If action contains $value this parameter is
  2963. # substituted by the original value of the datapoint which has
  2964. # triggered the action.
  2965. # assignment := channel.datapoint=expression
  2966. ######################################################################
  2967. sub HMCCU_UpdatePeers ($$$$)
  2968. {
  2969. my ($clt_hash, $chndpt, $val, $peerattr) = @_;
  2970. my $fnc = "UpdatePeers";
  2971. my $io_hash = HMCCU_GetHash ($clt_hash);
  2972. HMCCU_Trace ($clt_hash, 2, $fnc, "chndpt=$chndpt val=$val peer=$peerattr");
  2973. my @rules = split (/[;\n]+/, $peerattr);
  2974. foreach my $r (@rules) {
  2975. HMCCU_Trace ($clt_hash, 2, $fnc, "rule=$r");
  2976. my ($vars, $cond, $type, $act) = split (/:/, $r, 4);
  2977. next if (!defined ($act));
  2978. HMCCU_Trace ($clt_hash, 2, $fnc, "vars=$vars, cond=$cond, type=$type, act=$act");
  2979. next if ($cond !~ /$chndpt/);
  2980. # Check if rule is affected by datapoint update
  2981. my $ex = 0;
  2982. foreach my $dpt (split (",", $vars)) {
  2983. HMCCU_Trace ($clt_hash, 2, $fnc, "dpt=$dpt");
  2984. $ex = 1 if ($ex == 0 && $dpt eq $chndpt);
  2985. if (!exists ($clt_hash->{hmccu}{dp}{$dpt})) {
  2986. HMCCU_Trace ($clt_hash, 2, $fnc, "Datapoint $dpt does not exist on hash");
  2987. }
  2988. last if ($ex == 1);
  2989. }
  2990. next if (! $ex);
  2991. # Substitute variables and evaluate condition
  2992. $cond = HMCCU_SubstVariables ($clt_hash, $cond, $vars);
  2993. my $e = eval "$cond";
  2994. HMCCU_Trace ($clt_hash, 2, $fnc, "eval $cond = $e") if (defined ($e));
  2995. HMCCU_Trace ($clt_hash, 2, $fnc, "Error in eval $cond") if (!defined ($e));
  2996. HMCCU_Trace ($clt_hash, 2, $fnc, "NoMatch in eval $cond") if (defined ($e) && $e eq '');
  2997. next if (!defined ($e) || $e eq '');
  2998. # Substitute variables and execute action
  2999. if ($type eq 'ccu' || $type eq 'hmccu') {
  3000. my ($aobj, $aexp) = split (/=/, $act);
  3001. $aexp =~ s/\$value/$val/g;
  3002. $aexp = HMCCU_SubstVariables ($clt_hash, $aexp, $vars);
  3003. HMCCU_Trace ($clt_hash, 2, $fnc, "set $aobj to $aexp");
  3004. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($io_hash, "$type:$aobj",
  3005. $HMCCU_FLAG_INTERFACE);
  3006. next if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD);
  3007. HMCCU_SetMultipleDatapoints ($clt_hash, { "001.$int.$add:$chn.$dpt" => $aexp });
  3008. }
  3009. elsif ($type eq 'fhem') {
  3010. $act =~ s/\$value/$val/g;
  3011. $act = HMCCU_SubstVariables ($clt_hash, $act, $vars);
  3012. HMCCU_Trace ($clt_hash, 2, $fnc, "Execute command $act");
  3013. AnalyzeCommandChain (undef, $act);
  3014. }
  3015. }
  3016. }
  3017. ######################################################################
  3018. # Get list of valid RPC interfaces.
  3019. # Binary interfaces are ignored if internal RPC server is used.
  3020. # Default interface is BidCos-RF.
  3021. ######################################################################
  3022. sub HMCCU_GetRPCInterfaceList ($)
  3023. {
  3024. my ($hash) = @_;
  3025. my $name = $hash->{NAME};
  3026. my @interfaces = ($HMCCU_RPC_INTERFACE_DEFAULT);
  3027. my $ccuflags = HMCCU_GetFlags ($name);
  3028. if (defined ($hash->{hmccu}{rpcports})) {
  3029. foreach my $p (split (',', $hash->{hmccu}{rpcports})) {
  3030. my $ifname = HMCCU_GetRPCServerInfo ($hash, $p, 'name');
  3031. next if (!defined ($ifname));
  3032. my $iftype = HMCCU_GetRPCServerInfo ($hash, $ifname, 'type');
  3033. next if ($ifname eq $HMCCU_RPC_INTERFACE_DEFAULT ||
  3034. ($iftype eq 'B' && $ccuflags !~ /(extrpc|procrpc)/) ||
  3035. !exists ($hash->{hmccu}{interfaces}{$ifname}));
  3036. push (@interfaces, $ifname);
  3037. }
  3038. }
  3039. return @interfaces;
  3040. }
  3041. ######################################################################
  3042. # Get list of valid RPC ports.
  3043. # Binary interfaces are ignored if internal RPC server is used.
  3044. # Default port is 2001.
  3045. ######################################################################
  3046. sub HMCCU_GetRPCPortList ($)
  3047. {
  3048. my ($hash) = @_;
  3049. my $name = $hash->{NAME};
  3050. my @ports = ($HMCCU_RPC_PORT_DEFAULT);
  3051. my $ccuflags = HMCCU_GetFlags ($name);
  3052. if (defined ($hash->{hmccu}{rpcports})) {
  3053. foreach my $p (split (',', $hash->{hmccu}{rpcports})) {
  3054. my $ifname = HMCCU_GetRPCServerInfo ($hash, $p, 'name');
  3055. next if (!defined ($ifname));
  3056. my $iftype = HMCCU_GetRPCServerInfo ($hash, $ifname, 'type');
  3057. next if ($p == $HMCCU_RPC_PORT_DEFAULT ||
  3058. ($iftype eq 'B' && $ccuflags !~ /(extrpc|procrpc)/) ||
  3059. !exists ($hash->{hmccu}{interfaces}{$ifname}));
  3060. push (@ports, $p);
  3061. }
  3062. }
  3063. return @ports;
  3064. }
  3065. ######################################################################
  3066. # Build RPC callback URL
  3067. # Parameter hash might be a HMCCU or a HMCCURPC hash.
  3068. ######################################################################
  3069. sub HMCCU_GetRPCCallbackURL ($$$$$)
  3070. {
  3071. my ($hash, $localaddr, $cbport, $clkey, $iface) = @_;
  3072. return undef if (!defined ($hash));
  3073. my $hmccu_hash = $hash->{TYPE} eq 'HMCCURPC' ? $hash->{IODev} : $hash;
  3074. return undef if (!exists ($hmccu_hash->{hmccu}{interfaces}{$iface}) &&
  3075. !exists ($hmccu_hash->{hmccu}{ifports}{$iface}));
  3076. my $ifname = $iface =~ /^[0-9]+$/ ? $hmccu_hash->{hmccu}{ifports}{$iface} : $iface;
  3077. return undef if (!exists ($hmccu_hash->{hmccu}{interfaces}{$ifname}));
  3078. return $hmccu_hash->{hmccu}{interfaces}{$ifname}{prot}."://$localaddr:$cbport/fh".
  3079. $hmccu_hash->{hmccu}{interfaces}{$ifname}{port};
  3080. }
  3081. ######################################################################
  3082. # Get RPC server information.
  3083. # Parameter iface can be a port number or an interface name.
  3084. # Valid values for info are:
  3085. # url, port, prot, host, type, name, flags, device.
  3086. ######################################################################
  3087. sub HMCCU_GetRPCServerInfo ($$$)
  3088. {
  3089. my ($hash, $iface, $info) = @_;
  3090. return undef if (!defined ($hash));
  3091. return undef if (!exists ($hash->{hmccu}{interfaces}{$iface}) &&
  3092. !exists ($hash->{hmccu}{ifports}{$iface}));
  3093. my $ifname = $iface =~ /^[0-9]+$/ ? $hash->{hmccu}{ifports}{$iface} : $iface;
  3094. return undef if (!exists ($hash->{hmccu}{interfaces}{$ifname}));
  3095. return $ifname if ($info eq 'name');
  3096. return $hash->{hmccu}{interfaces}{$ifname}{$info};
  3097. }
  3098. ######################################################################
  3099. # Check if RPC interface is of specified type.
  3100. # Parameter type is A for XML or B for binary.
  3101. ######################################################################
  3102. sub HMCCU_IsRPCType ($$$)
  3103. {
  3104. my ($hash, $iface, $type) = @_;
  3105. my $rpctype = HMCCU_GetRPCServerInfo ($hash, $iface, 'type');
  3106. return 0 if (!defined ($rpctype));
  3107. return $rpctype eq $type ? 1 : 0;
  3108. }
  3109. ######################################################################
  3110. # Register RPC callbacks at CCU if RPC-Server already in server loop
  3111. ######################################################################
  3112. sub HMCCU_RPCRegisterCallback ($)
  3113. {
  3114. my ($hash) = @_;
  3115. my $name = $hash->{NAME};
  3116. my $localaddr = $hash->{hmccu}{localaddr};
  3117. my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL2);
  3118. my @rpcports = HMCCU_GetRPCPortList ($hash);
  3119. foreach my $port (@rpcports) {
  3120. my $clkey = 'CB'.$port;
  3121. my $cburl = HMCCU_GetRPCCallbackURL ($hash, $localaddr, $hash->{hmccu}{rpc}{$clkey}{cbport},
  3122. $clkey, $port);
  3123. next if (!defined ($cburl));
  3124. my $url = HMCCU_GetRPCServerInfo ($hash, $port, 'url');
  3125. next if (!defined ($url));
  3126. my $rpcflags = HMCCU_GetRPCServerInfo ($hash, $port, 'flags');
  3127. if ($hash->{hmccu}{rpc}{$clkey}{loop} == 1 ||
  3128. $hash->{hmccu}{rpc}{$clkey}{state} eq "register") {
  3129. $hash->{hmccu}{rpc}{$clkey}{port} = $port;
  3130. $hash->{hmccu}{rpc}{$clkey}{clurl} = $url;
  3131. $hash->{hmccu}{rpc}{$clkey}{cburl} = $cburl;
  3132. $hash->{hmccu}{rpc}{$clkey}{loop} = 2;
  3133. $hash->{hmccu}{rpc}{$clkey}{state} = $rpcflags =~ /forceInit/ ? "running" : "registered";
  3134. Log3 $name, 1, "HMCCU: Registering callback $cburl with ID $clkey at $url";
  3135. my $rpcclient = RPC::XML::Client->new ($url);
  3136. $rpcclient->send_request ("init", $cburl, $clkey);
  3137. Log3 $name, 1, "HMCCU: RPC callback with URL $cburl initialized";
  3138. }
  3139. }
  3140. # Schedule reading of RPC queue
  3141. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  3142. }
  3143. ######################################################################
  3144. # Deregister RPC callbacks at CCU
  3145. ######################################################################
  3146. sub HMCCU_RPCDeRegisterCallback ($)
  3147. {
  3148. my ($hash) = @_;
  3149. my $name = $hash->{NAME};
  3150. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  3151. my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  3152. if (exists ($rpchash->{cburl}) && $rpchash->{cburl} ne '') {
  3153. my $port = $rpchash->{port};
  3154. my $rpcclient = RPC::XML::Client->new ($rpchash->{clurl});
  3155. Log3 $name, 1, "HMCCU: Deregistering RPC server ".$rpchash->{cburl}.
  3156. " at ".$rpchash->{clurl};
  3157. $rpcclient->send_request("init", $rpchash->{cburl});
  3158. $rpchash->{cburl} = '';
  3159. $rpchash->{clurl} = '';
  3160. $rpchash->{cbport} = 0;
  3161. }
  3162. }
  3163. }
  3164. ######################################################################
  3165. # Initialize statistic counters
  3166. ######################################################################
  3167. sub HMCCU_ResetCounters ($)
  3168. {
  3169. my ($hash) = @_;
  3170. my @counters = ('total', 'EV', 'ND', 'IN', 'DD', 'RA', 'RD', 'UD', 'EX', 'SL', 'ST');
  3171. foreach my $cnt (@counters) {
  3172. $hash->{hmccu}{ev}{$cnt} = 0;
  3173. }
  3174. delete $hash->{hmccu}{evs};
  3175. delete $hash->{hmccu}{evr};
  3176. $hash->{hmccu}{evtimeout} = 0;
  3177. $hash->{hmccu}{evtime} = 0;
  3178. }
  3179. ######################################################################
  3180. # Start external RPC server via RPC device.
  3181. # Return number of RPC servers or 0 on error.
  3182. ######################################################################
  3183. sub HMCCU_StartExtRPCServer ($)
  3184. {
  3185. my ($hash) = @_;
  3186. my $name = $hash->{NAME};
  3187. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  3188. my $attrset = 0;
  3189. # Change RPC type to procrpc
  3190. if ($ccuflags =~ /extrpc/) {
  3191. $ccuflags =~ s/extrpc/procrpc/g;
  3192. CommandAttr (undef, "$name ccuflags $ccuflags");
  3193. $attrset = 1;
  3194. # Disable existing devices of type HMCCURPC
  3195. foreach my $d (keys %defs) {
  3196. my $ch = $defs{$d};
  3197. next if (!exists ($ch->{TYPE}) || !exists ($ch->{NAME}));
  3198. next if ($ch->{TYPE} ne 'HMCCURPC');
  3199. CommandAttr (undef, $ch->{NAME}." disable 1") if (IsDisabled ($ch->{NAME}) != 1);
  3200. }
  3201. }
  3202. # if ($ccuflags =~ /(extrpc|procrpc)/) {
  3203. # Search RPC device. Create one if none exists
  3204. # my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 1, undef);
  3205. # return HMCCU_Log ($hash, 0, "Can't find or create HMCCURPC device", 0) if ($rpcdev eq '');
  3206. # CommandSave (undef, undef) if ($save);
  3207. #
  3208. # my ($rc, $msg) = HMCCURPC_StartRPCServer ($defs{$rpcdev});
  3209. # HMCCU_SetRPCState ($hash, 'starting') if ($rc);
  3210. # Log3 $name, 0, "HMCCURPC: $msg" if (!$rc && defined ($msg));
  3211. # return $rc;
  3212. # }
  3213. # elsif ($ccuflags =~ /procrpc/) {
  3214. my $c = 0;
  3215. my $d = 0;
  3216. my $s = 0;
  3217. my @iflist = HMCCU_GetRPCInterfaceList ($hash);
  3218. foreach my $ifname1 (@iflist) {
  3219. my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 1, $ifname1);
  3220. next if ($rpcdev eq '' || !defined ($hash->{hmccu}{interfaces}{$ifname1}{device}));
  3221. $d++;
  3222. $s++ if ($save);
  3223. }
  3224. # Save FHEM config if new RPC devices were defined or attribute has changed
  3225. if ($s > 0 || $attrset) {
  3226. HMCCU_Log ($hash, 1, "Saving FHEM config", 0);
  3227. CommandSave (undef, undef);
  3228. }
  3229. if ($d == scalar (@iflist)) {
  3230. foreach my $ifname2 (@iflist) {
  3231. my $dh = $defs{$hash->{hmccu}{interfaces}{$ifname2}{device}};
  3232. $hash->{hmccu}{interfaces}{$ifname2}{manager} = 'HMCCU';
  3233. my ($rc, $msg) = HMCCURPCPROC_StartRPCServer ($dh);
  3234. if (!$rc) {
  3235. HMCCU_SetRPCState ($hash, 'error', $ifname2, $msg);
  3236. }
  3237. else {
  3238. $c++;
  3239. }
  3240. }
  3241. HMCCU_SetRPCState ($hash, 'starting') if ($c > 0);
  3242. return $c;
  3243. }
  3244. else {
  3245. HMCCU_Log ($hash, 0, "Definition of some RPC devices failed", undef);
  3246. }
  3247. # }
  3248. return 0;
  3249. }
  3250. ######################################################################
  3251. # Stop external RPC server via RPC device.
  3252. ######################################################################
  3253. sub HMCCU_StopExtRPCServer ($)
  3254. {
  3255. my ($hash) = @_;
  3256. my $name = $hash->{NAME};
  3257. if (HMCCU_IsFlag ($name, "(extrpc|procrpc)")) {
  3258. # return HMCCU_Log ($hash, 0, "Module HMCCURPC not loaded", 0) if (!exists ($modules{'HMCCURPC'}));
  3259. #
  3260. # my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, undef);
  3261. # return HMCCU_Log ($hash, 0, "Can't find RPC device", 0) if ($rpcdev eq '');
  3262. # HMCCU_SetRPCState ($hash, 'stopping');
  3263. # return HMCCURPC_StopRPCServer ($defs{$rpcdev});
  3264. # }
  3265. # elsif ($ccuflags =~ /procrpc/) {
  3266. return HMCCU_Log ($hash, 0, "Module HMCCURPCPROC not loaded", 0) if (!exists ($modules{'HMCCURPCPROC'}));
  3267. HMCCU_SetRPCState ($hash, 'stopping');
  3268. my $rc = 1;
  3269. my @iflist = HMCCU_GetRPCInterfaceList ($hash);
  3270. foreach my $ifname (@iflist) {
  3271. my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname);
  3272. if ($rpcdev eq '') {
  3273. Log3 $name, 0, "HMCCU: Can't find RPC device";
  3274. next;
  3275. }
  3276. $hash->{hmccu}{interfaces}{$ifname}{manager} = 'HMCCU';
  3277. $rc &= HMCCURPCPROC_StopRPCServer ($defs{$rpcdev});
  3278. }
  3279. return $rc;
  3280. }
  3281. }
  3282. ######################################################################
  3283. # Start internal file queue based RPC server.
  3284. # Return number of RPC server processes or 0 on error.
  3285. ######################################################################
  3286. sub HMCCU_StartIntRPCServer ($)
  3287. {
  3288. my ($hash) = @_;
  3289. my $name = $hash->{NAME};
  3290. # Timeouts
  3291. my $timeout = AttrVal ($name, 'rpctimeout', '0.01,0.25');
  3292. my ($to_read, $to_write) = split (",", $timeout);
  3293. $to_write = $to_read if (!defined ($to_write));
  3294. # Address and ports
  3295. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  3296. my $localaddr = AttrVal ($name, 'rpcserveraddr', '');
  3297. my $rpcserverport = AttrVal ($name, 'rpcserverport', 5400);
  3298. my $rpcinterval = AttrVal ($name, 'rpcinterval', $HMCCU_INIT_INTERVAL1);
  3299. my @rpcportlist = HMCCU_GetRPCPortList ($hash);
  3300. my $serveraddr = HMCCU_GetRPCServerInfo ($hash, $rpcportlist[0], 'host');
  3301. my $fork_cnt = 0;
  3302. HMCCU_Log ($hash, 1, "Internal RPC server is depricated and will be removed soon. Set ccuflags to procrpc", 0);
  3303. # Check for running RPC server processes
  3304. my @hm_pids;
  3305. my @hm_tids;
  3306. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  3307. if (scalar (@hm_pids) > 0) {
  3308. return HMCCU_Log ($hash, 0, "RPC server(s) already running with PIDs ".join (',', @hm_pids),
  3309. scalar (@hm_pids));
  3310. }
  3311. elsif (scalar (@hm_tids) > 0) {
  3312. return HMCCU_Log ($hash, 1, "RPC server(s) already running with TIDs ".join (',', @hm_tids),
  3313. 0);
  3314. }
  3315. # Detect local IP address
  3316. if ($localaddr eq '') {
  3317. $localaddr = HMCCU_TCPConnect ($serveraddr, $rpcportlist[0]);
  3318. return HMCCU_Log ($hash, 1, "Can't connect to RPC host $serveraddr port".$rpcportlist[0], 0)
  3319. if ($localaddr eq '');
  3320. }
  3321. $hash->{hmccu}{localaddr} = $localaddr;
  3322. my $ccunum = $hash->{CCUNum};
  3323. # Fork child processes
  3324. foreach my $port (@rpcportlist) {
  3325. my $clkey = 'CB'.$port;
  3326. my $rpcqueueport = $rpcqueue."_".$port."_".$ccunum;
  3327. my $callbackport = $rpcserverport+$port+($ccunum*10);
  3328. # Clear event queue
  3329. HMCCU_ResetRPCQueue ($hash, $port);
  3330. # Create child process
  3331. Log3 $name, 2, "HMCCU: Create child process with timeouts $to_read and $to_write";
  3332. my $child = SubProcess->new ({ onRun => \&HMCCU_CCURPC_OnRun,
  3333. onExit => \&HMCCU_CCURPC_OnExit, timeoutread => $to_read, timeoutwrite => $to_write });
  3334. $child->{serveraddr} = $serveraddr;
  3335. $child->{serverport} = $port;
  3336. $child->{callbackport} = $callbackport;
  3337. $child->{devname} = $name;
  3338. $child->{queue} = $rpcqueueport;
  3339. # Start child process
  3340. my $pid = $child->run ();
  3341. if (!defined ($pid)) {
  3342. Log3 $name, 1, "HMCCU: No RPC process for server $clkey started";
  3343. next;
  3344. }
  3345. Log3 $name, 0, "HMCCU: Child process for server $clkey started with PID $pid";
  3346. $fork_cnt++;
  3347. # Store child process parameters
  3348. $hash->{hmccu}{rpc}{$clkey}{child} = $child;
  3349. $hash->{hmccu}{rpc}{$clkey}{cbport} = $callbackport;
  3350. $hash->{hmccu}{rpc}{$clkey}{loop} = 0;
  3351. $hash->{hmccu}{rpc}{$clkey}{pid} = $pid;
  3352. $hash->{hmccu}{rpc}{$clkey}{queue} = $rpcqueueport;
  3353. $hash->{hmccu}{rpc}{$clkey}{state} = "starting";
  3354. push (@hm_pids, $pid);
  3355. }
  3356. $hash->{hmccu}{rpccount} = $fork_cnt;
  3357. if ($fork_cnt > 0) {
  3358. # Set internals
  3359. $hash->{RPCPID} = join (',', @hm_pids);
  3360. $hash->{RPCPRC} = "internal";
  3361. HMCCU_SetRPCState ($hash, 'starting');
  3362. # Initialize statistic counters
  3363. HMCCU_ResetCounters ($hash);
  3364. Log3 $name, 0, "RPC server(s) starting";
  3365. DoTrigger ($name, "RPC server starting");
  3366. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  3367. }
  3368. return $fork_cnt;
  3369. }
  3370. ######################################################################
  3371. # Stop RPC server(s) by sending SIGINT to process(es)
  3372. ######################################################################
  3373. sub HMCCU_StopRPCServer ($)
  3374. {
  3375. my ($hash) = @_;
  3376. my $name = $hash->{NAME};
  3377. my $pid = 0;
  3378. # Deregister callback URLs in CCU
  3379. HMCCU_RPCDeRegisterCallback ($hash);
  3380. # Send signal SIGINT to RPC server processes
  3381. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  3382. my $rpchash = \%{$hash->{hmccu}{rpc}{$clkey}};
  3383. if (exists ($rpchash->{pid}) && $rpchash->{pid} != 0) {
  3384. Log3 $name, 0, "HMCCU: Stopping RPC server $clkey with PID ".$rpchash->{pid};
  3385. kill ('INT', $rpchash->{pid});
  3386. $rpchash->{state} = "stopping";
  3387. }
  3388. else {
  3389. $rpchash->{state} = "inactive";
  3390. }
  3391. }
  3392. # Update status
  3393. HMCCU_SetRPCState ($hash, 'stopping') if ($hash->{hmccu}{rpccount} > 0);
  3394. # Wait
  3395. sleep (1);
  3396. # Check if processes were terminated
  3397. my @hm_pids;
  3398. my @hm_tids;
  3399. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  3400. if (scalar (@hm_pids) > 0) {
  3401. foreach my $pid (@hm_pids) {
  3402. Log3 $name, 0, "HMCCU: Stopping RPC server with PID $pid";
  3403. kill ('INT', $pid);
  3404. }
  3405. }
  3406. Log3 $name, 0, "HMCCU: Externally launched RPC server detected." if (scalar (@hm_tids) > 0);
  3407. # Wait
  3408. sleep (1);
  3409. # Kill the rest
  3410. @hm_pids = ();
  3411. @hm_tids = ();
  3412. if (HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids)) {
  3413. foreach my $pid (@hm_pids) {
  3414. kill ('KILL', $pid);
  3415. }
  3416. }
  3417. # Store number of running RPC servers
  3418. $hash->{hmccu}{rpccount} = HMCCU_IsRPCServerRunning ($hash, undef, undef);
  3419. return $hash->{hmccu}{rpccount} > 0 ? 0 : 1;
  3420. }
  3421. ######################################################################
  3422. # Check status of RPC server depending on internal RPCState.
  3423. # Return 1 if RPC server is stopping, starting or restarting. During
  3424. # this phases CCU reacts very slowly so any get or set command from
  3425. # HMCCU devices are disabled.
  3426. ######################################################################
  3427. sub HMCCU_IsRPCStateBlocking ($)
  3428. {
  3429. my ($hash) = @_;
  3430. if ($hash->{RPCState} eq "starting" ||
  3431. $hash->{RPCState} eq "restarting" ||
  3432. $hash->{RPCState} eq "stopping") {
  3433. return 1;
  3434. }
  3435. else {
  3436. return 0;
  3437. }
  3438. }
  3439. ######################################################################
  3440. # Check if RPC servers are running.
  3441. # Return number of running RPC servers. If paramters pids or tids are
  3442. # defined also return process or thread IDs.
  3443. ######################################################################
  3444. sub HMCCU_IsRPCServerRunning ($$$)
  3445. {
  3446. my ($hash, $pids, $tids) = @_;
  3447. my $name = $hash->{NAME};
  3448. my $c = 0;
  3449. my $ccuflags = HMCCU_GetFlags ($name);
  3450. if (HMCCU_IsFlag ($name, "(extrpc|procrpc)")) {
  3451. # @$tids = () if (defined ($tids));
  3452. # my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, undef);
  3453. # if ($rpcdev ne '') {
  3454. # my ($r, $a) = HMCCURPC_CheckThreadState ($defs{$rpcdev}, 6, 'running', $tids);
  3455. # $c = $r;
  3456. # }
  3457. # }
  3458. # elsif ($ccuflags =~ /procrpc/) {
  3459. @$pids = () if (defined ($pids));
  3460. my @iflist = HMCCU_GetRPCInterfaceList ($hash);
  3461. foreach my $ifname (@iflist) {
  3462. my ($rpcdev, $save) = HMCCU_GetRPCDevice ($hash, 0, $ifname);
  3463. next if ($rpcdev eq '');
  3464. my $rc = HMCCURPCPROC_CheckProcessState ($defs{$rpcdev}, 'running');
  3465. if ($rc < 0 || $rc > 1) {
  3466. push (@$pids, $rc);
  3467. $c++;
  3468. }
  3469. }
  3470. }
  3471. else {
  3472. @$pids = () if (defined ($pids));
  3473. foreach my $clkey (keys %{$hash->{hmccu}{rpc}}) {
  3474. if (defined ($hash->{hmccu}{rpc}{$clkey}{pid})) {
  3475. my $pid = $hash->{hmccu}{rpc}{$clkey}{pid};
  3476. if ($pid != 0 && kill (0, $pid)) {
  3477. push (@$pids, $pid) if (defined ($pids));
  3478. $c++;
  3479. }
  3480. }
  3481. }
  3482. }
  3483. return $c;
  3484. }
  3485. ######################################################################
  3486. # Get channels and datapoints of CCU device
  3487. ######################################################################
  3488. sub HMCCU_GetDeviceInfo ($$$)
  3489. {
  3490. my ($hash, $device, $ccuget) = @_;
  3491. my $name = $hash->{NAME};
  3492. my $devname = '';
  3493. my $response = '';
  3494. my $hmccu_hash = HMCCU_GetHash ($hash);
  3495. return '' if (!defined ($hmccu_hash));
  3496. my @devlist;
  3497. if ($hash->{ccuif} eq 'fhem' && exists ($hash->{ccugroup})) {
  3498. push @devlist, split (",", $hash->{ccugroup});
  3499. }
  3500. else {
  3501. push @devlist, $device;
  3502. }
  3503. return '' if (scalar (@devlist) == 0);
  3504. $ccuget = HMCCU_GetAttribute ($hmccu_hash, $hash, 'ccuget', 'Value') if ($ccuget eq 'Attr');
  3505. for my $dev (@devlist) {
  3506. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $dev, 0);
  3507. if ($flags == $HMCCU_FLAG_ADDRESS) {
  3508. $devname = HMCCU_GetDeviceName ($hmccu_hash, $add, '');
  3509. return '' if ($devname eq '');
  3510. }
  3511. else {
  3512. $devname = $nam;
  3513. }
  3514. $response .= HMCCU_HMScriptExt ($hmccu_hash, "!GetDeviceInfo",
  3515. { devname => $devname, ccuget => $ccuget });
  3516. HMCCU_Trace ($hash, 2, undef,
  3517. "Device=$devname Devname=$devname<br>".
  3518. "Script response = \n".$response."<br>".
  3519. "Script = GetDeviceInfo");
  3520. }
  3521. return $response;
  3522. }
  3523. ######################################################################
  3524. # Make device info readable
  3525. # n=number, b=bool, f=float, i=integer, s=string, a=alarm, p=presence
  3526. # e=enumeration
  3527. ######################################################################
  3528. sub HMCCU_FormatDeviceInfo ($)
  3529. {
  3530. my ($devinfo) = @_;
  3531. my %vtypes = (0, "n", 2, "b", 4, "f", 6, "a", 8, "n", 11, "s", 16, "i", 20, "s", 23, "p", 29, "e");
  3532. my $result = '';
  3533. my $c_oaddr = '';
  3534. foreach my $dpspec (split ("\n", $devinfo)) {
  3535. my ($c, $c_addr, $c_name, $d_name, $d_type, $d_value, $d_flags) = split (";", $dpspec);
  3536. if ($c_addr ne $c_oaddr) {
  3537. $result .= "CHN $c_addr $c_name\n";
  3538. $c_oaddr = $c_addr;
  3539. }
  3540. my $t = exists ($vtypes{$d_type}) ? $vtypes{$d_type} : $d_type;
  3541. $result .= " DPT {$t} $d_name = $d_value [$d_flags]\n";
  3542. }
  3543. return $result;
  3544. }
  3545. ######################################################################
  3546. # Get available firmware versions from EQ-3 server.
  3547. # Firmware version, date and download link are stored in hash
  3548. # {hmccu}{type}{$type} in elements {firmware}, {date} and {download}.
  3549. # Parameter type can be a regular expression matching valid Homematic
  3550. # device types in upper case letters. Default is '.*'.
  3551. # Return number of available firmware downloads.
  3552. ######################################################################
  3553. sub HMCCU_GetFirmwareVersions ($$)
  3554. {
  3555. my ($hash, $type) = @_;
  3556. my $name = $hash->{NAME};
  3557. my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  3558. my $url = "http://www.eq-3.de/service/downloads.html";
  3559. my $response = GetFileFromURL ($url, $ccureqtimeout, "suchtext=&suche_in=&downloadart=11");
  3560. my @download = $response =~ m/<a.href="(Downloads\/Software\/Firmware\/[^"]+)/g;
  3561. my $dc = 0;
  3562. my @ts = localtime (time);
  3563. $ts[4] += 1;
  3564. $ts[5] += 1900;
  3565. foreach my $dl (@download) {
  3566. my $dd = $ts[3];
  3567. my $mm = $ts[4];
  3568. my $yy = $ts[5];
  3569. my $fw;
  3570. my $date = "$dd.$mm.$yy";
  3571. my @path = split (/\//, $dl);
  3572. my $file = pop @path;
  3573. next if ($file !~ /(\.tgz|\.tar\.gz)/);
  3574. $file =~ s/_update_V?/\|/;
  3575. my ($dt, $rest) = split (/\|/, $file);
  3576. next if (!defined ($rest));
  3577. $dt =~ s/_/-/g;
  3578. $dt = uc($dt);
  3579. next if ($dt !~ /$type/);
  3580. if ($rest =~ /^([\d_]+)([0-9]{2})([0-9]{2})([0-9]{2})\./) {
  3581. # Filename with version and date
  3582. ($fw, $yy, $mm, $dd) = ($1, $2, $3, $4);
  3583. $yy += 2000 if ($yy < 100);
  3584. $date = "$dd.$mm.$yy";
  3585. $fw =~ s/_$//;
  3586. }
  3587. elsif ($rest =~ /^([\d_]+)\./) {
  3588. # Filename with version
  3589. $fw = $1;
  3590. }
  3591. else {
  3592. $fw = $rest;
  3593. }
  3594. $fw =~ s/_/\./g;
  3595. # Compare firmware dates
  3596. if (exists ($hash->{hmccu}{type}{$dt}{date})) {
  3597. my ($dd1, $mm1, $yy1) = split (/\./, $hash->{hmccu}{type}{$dt}{date});
  3598. my $v1 = $yy1*10000+$mm1*100+$dd1;
  3599. my $v2 = $yy*10000+$mm*100+$dd;
  3600. next if ($v1 > $v2);
  3601. }
  3602. $dc++;
  3603. $hash->{hmccu}{type}{$dt}{firmware} = $fw;
  3604. $hash->{hmccu}{type}{$dt}{date} = $date;
  3605. $hash->{hmccu}{type}{$dt}{download} = $dl;
  3606. }
  3607. return $dc;
  3608. }
  3609. ######################################################################
  3610. # Read CCU device identified by device or channel name via Homematic
  3611. # Script.
  3612. # Return (device count, channel count) or (-1, -1) on error.
  3613. ######################################################################
  3614. sub HMCCU_GetDevice ($$)
  3615. {
  3616. my ($hash, $name) = @_;
  3617. my $devcount = 0;
  3618. my $chncount = 0;
  3619. my $devname;
  3620. my $devtype;
  3621. my %objects = ();
  3622. my $response = HMCCU_HMScriptExt ($hash, "!GetDevice", { name => $name });
  3623. return (-1, -1) if ($response eq '' || $response =~ /^ERROR:.*/);
  3624. my @scrlines = split /\n/,$response;
  3625. foreach my $hmdef (@scrlines) {
  3626. my @hmdata = split /;/,$hmdef;
  3627. next if (scalar (@hmdata) == 0);
  3628. my $typeprefix = '';
  3629. if ($hmdata[0] eq 'D') {
  3630. next if (scalar (@hmdata) != 6);
  3631. # 1=Interface 2=Device-Address 3=Device-Name 4=Device-Type 5=Channel-Count
  3632. $objects{$hmdata[2]}{addtype} = 'dev';
  3633. $objects{$hmdata[2]}{channels} = $hmdata[5];
  3634. $objects{$hmdata[2]}{flag} = 'N';
  3635. $objects{$hmdata[2]}{interface} = $hmdata[1];
  3636. $objects{$hmdata[2]}{name} = $hmdata[3];
  3637. $typeprefix = "CUX-" if ($hmdata[2] =~ /^CUX/);
  3638. $typeprefix = "HVL-" if ($hmdata[1] eq 'HVL');
  3639. $objects{$hmdata[2]}{type} = $typeprefix . $hmdata[4];
  3640. $objects{$hmdata[2]}{chndir} = 0;
  3641. $devname = $hmdata[3];
  3642. $devtype = $typeprefix . $hmdata[4];
  3643. }
  3644. elsif ($hmdata[0] eq 'C') {
  3645. next if (scalar (@hmdata) != 4);
  3646. # 1=Channel-Address 2=Channel-Name 3=Direction
  3647. $objects{$hmdata[1]}{addtype} = 'chn';
  3648. $objects{$hmdata[1]}{channels} = 1;
  3649. $objects{$hmdata[1]}{flag} = 'N';
  3650. $objects{$hmdata[1]}{name} = $hmdata[2];
  3651. $objects{$hmdata[1]}{valid} = 1;
  3652. $objects{$hmdata[1]}{chndir} = $hmdata[3];
  3653. }
  3654. }
  3655. if (scalar (keys %objects) > 0) {
  3656. # Update HMCCU device tables
  3657. ($devcount, $chncount) = HMCCU_UpdateDeviceTable ($hash, \%objects);
  3658. # Read available datapoints for device type
  3659. HMCCU_GetDatapointList ($hash, $devname, $devtype) if (defined ($devname) && defined ($devtype));
  3660. }
  3661. return ($devcount, $chncount);
  3662. }
  3663. ######################################################################
  3664. # Read list of CCU devices, channels, interfaces, programs and groups
  3665. # via Homematic Script.
  3666. # Update data of client devices if not current.
  3667. # Return counters (devices, channels, interfaces, programs, groups)
  3668. # or (-1, -1, -1, -1, -1) on error.
  3669. ######################################################################
  3670. sub HMCCU_GetDeviceList ($)
  3671. {
  3672. my ($hash) = @_;
  3673. my $name = $hash->{NAME};
  3674. my $devcount = 0;
  3675. my $chncount = 0;
  3676. my $ifcount = 0;
  3677. my $prgcount = 0;
  3678. my $gcount = 0;
  3679. my %objects = ();
  3680. # Read devices, channels, interfaces and groups from CCU
  3681. my $response = HMCCU_HMScriptExt ($hash, "!GetDeviceList", undef);
  3682. return (-1, -1, -1, -1, -1) if ($response eq '' || $response =~ /^ERROR:.*/);
  3683. my $groups = HMCCU_HMScriptExt ($hash, "!GetGroupDevices", undef);
  3684. # CCU is reachable
  3685. $hash->{ccustate} = 'active';
  3686. # Delete old entries
  3687. %{$hash->{hmccu}{dev}} = ();
  3688. %{$hash->{hmccu}{adr}} = ();
  3689. %{$hash->{hmccu}{grp}} = ();
  3690. %{$hash->{hmccu}{prg}} = ();
  3691. $hash->{hmccu}{updatetime} = time ();
  3692. # Device hash elements for HMCCU_UpdateDeviceTable():
  3693. #
  3694. # {address}{flag} := [N, D, R]
  3695. # {address}{addtype} := [chn, dev]
  3696. # {address}{channels} := Number of channels
  3697. # {address}{name} := Device or channel name
  3698. # {address}{type} := Homematic device type
  3699. # {address}{usetype} := Usage type
  3700. # {address}{interface} := Device interface ID
  3701. # {address}{firmware} := Firmware version of device
  3702. # {address}{version} := Version of RPC device description
  3703. # {address}{rxmode} := Transmit mode
  3704. # {address}{chndir} := Channel direction: 1=sensor 2=actor 0=none
  3705. my @scrlines = split /\n/,$response;
  3706. foreach my $hmdef (@scrlines) {
  3707. my @hmdata = split /;/,$hmdef;
  3708. next if (scalar (@hmdata) == 0);
  3709. my $typeprefix = '';
  3710. if ($hmdata[0] eq 'D') {
  3711. # Device
  3712. next if (scalar (@hmdata) != 6);
  3713. # 1=Interface 2=Device-Address 3=Device-Name 4=Device-Type 5=Channel-Count
  3714. $objects{$hmdata[2]}{addtype} = 'dev';
  3715. $objects{$hmdata[2]}{channels} = $hmdata[5];
  3716. $objects{$hmdata[2]}{flag} = 'N';
  3717. $objects{$hmdata[2]}{interface} = $hmdata[1];
  3718. $objects{$hmdata[2]}{name} = $hmdata[3];
  3719. $typeprefix = "CUX-" if ($hmdata[2] =~ /^CUX/);
  3720. $typeprefix = "HVL-" if ($hmdata[1] eq 'HVL');
  3721. $objects{$hmdata[2]}{type} = $typeprefix . $hmdata[4];
  3722. $objects{$hmdata[2]}{chndir} = 0;
  3723. # CCU information (address = BidCoS-RF)
  3724. if ($hmdata[2] eq 'BidCoS-RF') {
  3725. $hash->{ccuname} = $hmdata[3];
  3726. $hash->{ccuaddr} = $hmdata[2];
  3727. $hash->{ccuif} = $hmdata[1];
  3728. }
  3729. }
  3730. elsif ($hmdata[0] eq 'C') {
  3731. # Channel
  3732. next if (scalar (@hmdata) != 4);
  3733. # 1=Channel-Address 2=Channel-Name 3=Direction
  3734. $objects{$hmdata[1]}{addtype} = 'chn';
  3735. $objects{$hmdata[1]}{channels} = 1;
  3736. $objects{$hmdata[1]}{flag} = 'N';
  3737. $objects{$hmdata[1]}{name} = $hmdata[2];
  3738. $objects{$hmdata[1]}{valid} = 1;
  3739. $objects{$hmdata[1]}{chndir} = $hmdata[3];
  3740. }
  3741. elsif ($hmdata[0] eq 'I') {
  3742. # Interface
  3743. next if (scalar (@hmdata) != 4);
  3744. # 1=Interface-Name 2=Interface Info 3=URL
  3745. my $ifurl = $hmdata[3];
  3746. if ($ifurl =~ /^([^:]+):\/\/([^:]+):([0-9]+)/) {
  3747. my ($prot, $ipaddr, $port) = ($1, $2, $3);
  3748. next if (!defined ($port) || $port eq '');
  3749. if ($port >= 10000) {
  3750. $port -= 30000;
  3751. $ifurl =~ s/:3$port/:$port/;
  3752. }
  3753. if ($hash->{ccuip} ne 'N/A') {
  3754. $ifurl =~ s/127\.0\.0\.1/$hash->{ccuip}/;
  3755. $ipaddr =~ s/127\.0\.0\.1/$hash->{ccuip}/;
  3756. }
  3757. else {
  3758. $ifurl =~ s/127\.0\.0\.1/$hash->{host}/;
  3759. $ipaddr =~ s/127\.0\.0\.1/$hash->{host}/;
  3760. }
  3761. if ($HMCCU_RPC_FLAG{$port} =~ /forceASCII/) {
  3762. $ifurl =~ s/xmlrpc_bin/xmlrpc/;
  3763. $prot = "xmlrpc";
  3764. }
  3765. # Perl module RPC::XML::Client.pm does not support URLs starting with xmlrpc://
  3766. $ifurl =~ s/xmlrpc:/http:/;
  3767. $prot =~ s/^xmlrpc$/http/;
  3768. $hash->{hmccu}{interfaces}{$hmdata[1]}{url} = $ifurl;
  3769. $hash->{hmccu}{interfaces}{$hmdata[1]}{prot} = $prot;
  3770. $hash->{hmccu}{interfaces}{$hmdata[1]}{type} = $prot eq 'http' ? 'A' : 'B';
  3771. $hash->{hmccu}{interfaces}{$hmdata[1]}{port} = $port;
  3772. $hash->{hmccu}{interfaces}{$hmdata[1]}{host} = $ipaddr;
  3773. $hash->{hmccu}{interfaces}{$hmdata[1]}{state} = 'inactive';
  3774. $hash->{hmccu}{interfaces}{$hmdata[1]}{manager} = 'null';
  3775. $hash->{hmccu}{interfaces}{$hmdata[1]}{flags} = $HMCCU_RPC_FLAG{$port};
  3776. $hash->{hmccu}{ifports}{$port} = $hmdata[1];
  3777. $ifcount++;
  3778. }
  3779. }
  3780. elsif ($hmdata[0] eq 'P') {
  3781. # Program
  3782. next if (scalar (@hmdata) != 4);
  3783. # 1=Program-Name 2=Active-Flag 3=Internal-Flag
  3784. $hash->{hmccu}{prg}{$hmdata[1]}{active} = $hmdata[2];
  3785. $hash->{hmccu}{prg}{$hmdata[1]}{internal} = $hmdata[3];
  3786. $prgcount++;
  3787. }
  3788. }
  3789. if (scalar (keys %objects) > 0) {
  3790. # Update some CCU I/O device information
  3791. $hash->{ccuinterfaces} = join (',', keys %{$hash->{hmccu}{interfaces}});
  3792. # Update HMCCU device tables
  3793. ($devcount, $chncount) = HMCCU_UpdateDeviceTable ($hash, \%objects);
  3794. # Read available datapoints for each device type
  3795. # This will lead to problems if some devices have different firmware versions
  3796. # or links to system variables !
  3797. HMCCU_GetDatapointList ($hash, undef, undef);
  3798. }
  3799. # Store group configurations
  3800. if ($groups !~ /^ERROR:.*/ && $groups ne '') {
  3801. my @gnames = ($groups =~ m/"NAME":"([^"]+)"/g);
  3802. my @gmembers = ($groups =~ m/"groupMembers":\[[^\]]+\]/g);
  3803. my @gtypes = ($groups =~ m/"groupType":\{"id":"([^"]+)"/g);
  3804. foreach my $gm (@gmembers) {
  3805. my $gn = shift @gnames;
  3806. my $gt = shift @gtypes;
  3807. my @ml = ($gm =~ m/,"id":"([^"]+)"/g);
  3808. $hash->{hmccu}{grp}{$gn}{type} = $gt;
  3809. $hash->{hmccu}{grp}{$gn}{devs} = join (',', @ml);
  3810. $gcount++;
  3811. }
  3812. }
  3813. # Store asset counters
  3814. $hash->{hmccu}{ccu}{devcount} = $devcount;
  3815. $hash->{hmccu}{ccu}{chncount} = $chncount;
  3816. $hash->{hmccu}{ccu}{ifcount} = $ifcount;
  3817. $hash->{hmccu}{ccu}{prgcount} = $prgcount;
  3818. $hash->{hmccu}{ccu}{gcount} = $gcount;
  3819. readingsBeginUpdate ($hash);
  3820. readingsBulkUpdate ($hash, "count_devices", $devcount);
  3821. readingsBulkUpdate ($hash, "count_channels", $chncount);
  3822. readingsBulkUpdate ($hash, "count_interfaces", $ifcount);
  3823. readingsBulkUpdate ($hash, "count_programs", $prgcount);
  3824. readingsBulkUpdate ($hash, "count_groups", $gcount);
  3825. readingsEndUpdate ($hash, 1);
  3826. return ($devcount, $chncount, $ifcount, $prgcount, $gcount);
  3827. }
  3828. ######################################################################
  3829. # Read list of datapoints for all or one CCU device type(s).
  3830. # Function must not be called before GetDeviceList.
  3831. # Return number of datapoints.
  3832. ######################################################################
  3833. sub HMCCU_GetDatapointList ($$$)
  3834. {
  3835. my ($hash, $devname, $devtype) = @_;
  3836. my $name = $hash->{NAME};
  3837. my @devunique;
  3838. if (defined ($devname) && defined ($devtype)) {
  3839. return 0 if (exists ($hash->{hmccu}{dp}{$devtype}));
  3840. push @devunique, $devname;
  3841. }
  3842. else {
  3843. if (exists ($hash->{hmccu}{dp})) {
  3844. delete $hash->{hmccu}{dp};
  3845. }
  3846. # Select one device for each device type
  3847. my %alltypes;
  3848. foreach my $add (sort keys %{$hash->{hmccu}{dev}}) {
  3849. next if ($hash->{hmccu}{dev}{$add}{addtype} ne 'dev');
  3850. my $dt = $hash->{hmccu}{dev}{$add}{type};
  3851. if (defined ($dt)) {
  3852. if ($dt ne '' && !exists ($alltypes{$dt})) {
  3853. $alltypes{$dt} = 1;
  3854. push @devunique, $hash->{hmccu}{dev}{$add}{name};
  3855. }
  3856. }
  3857. else {
  3858. Log3 $name, 2, "HMCCU: Corrupt or invalid entry in device table for device $add";
  3859. }
  3860. }
  3861. }
  3862. if (scalar (@devunique) == 0) {
  3863. Log3 $name, 2, "HMCCU: No device types found in device table. Cannot read datapoints.";
  3864. return 0;
  3865. }
  3866. my $devlist = join (',', @devunique);
  3867. my $response = HMCCU_HMScriptExt ($hash, "!GetDatapointList",
  3868. { list => $devlist });
  3869. if ($response eq '' || $response =~ /^ERROR:.*/) {
  3870. Log3 $name, 2, "HMCCU: Cannot get datapoint list";
  3871. return 0;
  3872. }
  3873. my $c = 0;
  3874. foreach my $dpspec (split /\n/,$response) {
  3875. my ($iface, $chna, $devt, $devc, $dptn, $dptt, $dpto) = split (";", $dpspec);
  3876. $devt = "CUX-".$devt if ($iface eq 'CUxD');
  3877. $devt = "HVL-".$devt if ($iface eq 'HVL');
  3878. $hash->{hmccu}{dp}{$devt}{spc}{ontime} = $devc.".".$dptn if ($dptn eq "ON_TIME");
  3879. $hash->{hmccu}{dp}{$devt}{spc}{ramptime} = $devc.".".$dptn if ($dptn eq "RAMP_TIME");
  3880. $hash->{hmccu}{dp}{$devt}{spc}{submit} = $devc.".".$dptn if ($dptn eq "SUBMIT");
  3881. $hash->{hmccu}{dp}{$devt}{spc}{level} = $devc.".".$dptn if ($dptn eq "LEVEL");
  3882. $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{type} = $dptt;
  3883. $hash->{hmccu}{dp}{$devt}{ch}{$devc}{$dptn}{oper} = $dpto;
  3884. if (exists ($hash->{hmccu}{dp}{$devt}{cnt}{$dptn})) {
  3885. $hash->{hmccu}{dp}{$devt}{cnt}{$dptn}++;
  3886. }
  3887. else {
  3888. $hash->{hmccu}{dp}{$devt}{cnt}{$dptn} = 1;
  3889. }
  3890. $c++;
  3891. }
  3892. return $c;
  3893. }
  3894. ######################################################################
  3895. # Check if device/channel name or address is valid and refers to an
  3896. # existing device or channel.
  3897. # mode: Bit combination: 1=Address 2=Name 4=Special address
  3898. ######################################################################
  3899. sub HMCCU_IsValidDeviceOrChannel ($$$)
  3900. {
  3901. my ($hash, $param, $mode) = @_;
  3902. return HMCCU_IsValidDevice ($hash, $param, $mode) || HMCCU_IsValidChannel ($hash, $param, $mode) ? 1 : 0;
  3903. }
  3904. ######################################################################
  3905. # Check if device name or address is valid and refers to an existing
  3906. # device.
  3907. # mode: Bit combination: 1=Address 2=Name 4=Special address
  3908. ######################################################################
  3909. sub HMCCU_IsValidDevice ($$$)
  3910. {
  3911. my ($hash, $param, $mode) = @_;
  3912. # Address
  3913. if ($mode & $HMCCU_FL_STADDRESS) {
  3914. # Address with interface
  3915. if (HMCCU_IsDevAddr ($param, 1)) {
  3916. my ($i, $a) = split (/\./, $param);
  3917. return 0 if (! exists ($hash->{hmccu}{dev}{$a}));
  3918. return $hash->{hmccu}{dev}{$a}{valid};
  3919. }
  3920. # Address without interface
  3921. if (HMCCU_IsDevAddr ($param, 0)) {
  3922. return 0 if (! exists ($hash->{hmccu}{dev}{$param}));
  3923. return $hash->{hmccu}{dev}{$param}{valid};
  3924. }
  3925. # Special address for Non-Homematic devices
  3926. if (($mode & $HMCCU_FL_EXADDRESS) && exists ($hash->{hmccu}{dev}{$param})) {
  3927. return $hash->{hmccu}{dev}{$param}{valid} && $hash->{hmccu}{dev}{$param}{addtype} eq 'dev' ? 1 : 0;
  3928. }
  3929. }
  3930. # Name
  3931. if (($mode & 2) && exists ($hash->{hmccu}{adr}{$param})) {
  3932. return $hash->{hmccu}{adr}{$param}{valid} && $hash->{hmccu}{adr}{$param}{addtype} eq 'dev' ? 1 : 0;
  3933. }
  3934. return 0;
  3935. }
  3936. ######################################################################
  3937. # Check if channel name or address is valid and refers to an existing
  3938. # channel.
  3939. # mode: Bit combination: 1=Address 2=Name 4=Special address
  3940. ######################################################################
  3941. sub HMCCU_IsValidChannel ($$$)
  3942. {
  3943. my ($hash, $param, $mode) = @_;
  3944. # Standard address for Homematic devices
  3945. if ($mode & $HMCCU_FL_STADDRESS) {
  3946. # Address with interface
  3947. if (($mode & $HMCCU_FL_STADDRESS) && HMCCU_IsChnAddr ($param, 1)) {
  3948. my ($i, $a) = split (/\./, $param);
  3949. return 0 if (! exists ($hash->{hmccu}{dev}{$a}));
  3950. return $hash->{hmccu}{dev}{$a}{valid};
  3951. }
  3952. # Address without interface
  3953. if (HMCCU_IsChnAddr ($param, 0)) {
  3954. return 0 if (! exists ($hash->{hmccu}{dev}{$param}));
  3955. return $hash->{hmccu}{dev}{$param}{valid};
  3956. }
  3957. }
  3958. # Special address for Non-Homematic devices
  3959. if (($mode & $HMCCU_FL_EXADDRESS) && exists ($hash->{hmccu}{dev}{$param})) {
  3960. return $hash->{hmccu}{dev}{$param}{valid} && $hash->{hmccu}{dev}{$param}{addtype} eq 'chn' ? 1 : 0;
  3961. }
  3962. # Name
  3963. if (($mode & 2) && exists ($hash->{hmccu}{adr}{$param})) {
  3964. return $hash->{hmccu}{adr}{$param}{valid} && $hash->{hmccu}{adr}{$param}{addtype} eq 'chn' ? 1 : 0;
  3965. }
  3966. return 0;
  3967. }
  3968. ######################################################################
  3969. # Get CCU parameters of device or channel.
  3970. # Returns list containing interface, deviceaddress, name, type and
  3971. # channels.
  3972. ######################################################################
  3973. sub HMCCU_GetCCUDeviceParam ($$)
  3974. {
  3975. my ($hash, $param) = @_;
  3976. my $name = $hash->{NAME};
  3977. my $devadd;
  3978. my $add = undef;
  3979. my $chn = undef;
  3980. if (HMCCU_IsDevAddr ($param, 1) || HMCCU_IsChnAddr ($param, 1)) {
  3981. my $i;
  3982. ($i, $add) = split (/\./, $param);
  3983. }
  3984. else {
  3985. if (HMCCU_IsDevAddr ($param, 0) || HMCCU_IsChnAddr ($param, 0)) {
  3986. $add = $param;
  3987. }
  3988. else {
  3989. if (exists ($hash->{hmccu}{adr}{$param})) {
  3990. # param is a device name
  3991. $add = $hash->{hmccu}{adr}{$param}{address};
  3992. }
  3993. elsif (exists ($hash->{hmccu}{dev}{$param})) {
  3994. # param is a non standard device or channel address
  3995. $add = $param;
  3996. }
  3997. }
  3998. }
  3999. return (undef, undef, undef, undef) if (!defined ($add));
  4000. ($devadd, $chn) = split (':', $add);
  4001. return (undef, undef, undef, undef) if (!defined ($devadd) ||
  4002. !exists ($hash->{hmccu}{dev}{$devadd}) || $hash->{hmccu}{dev}{$devadd}{valid} == 0);
  4003. return ($hash->{hmccu}{dev}{$devadd}{interface}, $add, $hash->{hmccu}{dev}{$add}{name},
  4004. $hash->{hmccu}{dev}{$devadd}{type}, $hash->{hmccu}{dev}{$add}{channels});
  4005. }
  4006. ######################################################################
  4007. # Get list of valid datapoints for device type.
  4008. # hash = hash of client or IO device
  4009. # devtype = Homematic device type
  4010. # chn = Channel number, -1=all channels
  4011. # oper = Valid operation: 1=Read, 2=Write, 4=Event
  4012. # dplistref = Reference for array with datapoints.
  4013. # Return number of datapoints.
  4014. ######################################################################
  4015. sub HMCCU_GetValidDatapoints ($$$$$)
  4016. {
  4017. my ($hash, $devtype, $chn, $oper, $dplistref) = @_;
  4018. my $hmccu_hash = HMCCU_GetHash ($hash);
  4019. return 0 if (HMCCU_IsFlag ($hmccu_hash->{NAME}, "dptnocheck"));
  4020. return 0 if (!exists ($hmccu_hash->{hmccu}{dp}));
  4021. return HMCCU_Log ($hash, 2, "chn undefined", 0) if (!defined ($chn));
  4022. if ($chn >= 0) {
  4023. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) {
  4024. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  4025. if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dp}{oper} & $oper) {
  4026. push @$dplistref, $dp;
  4027. }
  4028. }
  4029. }
  4030. }
  4031. else {
  4032. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype})) {
  4033. foreach my $ch (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}}) {
  4034. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}}) {
  4035. if ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper) {
  4036. push @$dplistref, $ch.".".$dp;
  4037. }
  4038. }
  4039. }
  4040. }
  4041. }
  4042. return scalar (@$dplistref);
  4043. }
  4044. ######################################################################
  4045. # Get datapoint attribute.
  4046. # Valid attributes are 'oper' or 'type'.
  4047. ######################################################################
  4048. sub HMCCU_GetDatapointAttr ($$$$$)
  4049. {
  4050. my ($hash, $devtype, $chnno, $dpt, $attr) = @_;
  4051. return undef if ($attr ne 'oper' && $attr ne 'type');
  4052. return undef if (!exists ($hash->{hmccu}{dp}{$devtype}));
  4053. return undef if (!exists ($hash->{hmccu}{dp}{$devtype}{ch}{$chnno}));
  4054. return undef if (!exists ($hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}));
  4055. return $hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}{$attr};
  4056. }
  4057. ######################################################################
  4058. # Find a datapoint for device type.
  4059. # hash = hash of client or IO device
  4060. # devtype = Homematic device type
  4061. # chn = Channel number, -1=all channels
  4062. # oper = Valid operation: 1=Read, 2=Write, 4=Event
  4063. # Return channel of first match or -1.
  4064. ######################################################################
  4065. sub HMCCU_FindDatapoint ($$$$$)
  4066. {
  4067. my ($hash, $devtype, $chn, $dpt, $oper) = @_;
  4068. my $hmccu_hash = HMCCU_GetHash ($hash);
  4069. return -1 if (!exists ($hmccu_hash->{hmccu}{dp}));
  4070. if ($chn >= 0) {
  4071. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn})) {
  4072. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}}) {
  4073. return $chn if ($dp eq $dpt &&
  4074. $hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chn}{$dp}{oper} & $oper);
  4075. }
  4076. }
  4077. }
  4078. else {
  4079. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype})) {
  4080. foreach my $ch (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}}) {
  4081. foreach my $dp (sort keys %{$hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}}) {
  4082. return $ch if ($dp eq $dpt &&
  4083. $hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$ch}{$dp}{oper} & $oper);
  4084. }
  4085. }
  4086. }
  4087. }
  4088. return -1;
  4089. }
  4090. ######################################################################
  4091. # Get channel number and datapoint name for special datapoint.
  4092. # Valid modes are ontime, ramptime, submit, level
  4093. ######################################################################
  4094. sub HMCCU_GetSwitchDatapoint ($$$)
  4095. {
  4096. my ($hash, $devtype, $mode) = @_;
  4097. my $hmccu_hash = HMCCU_GetHash ($hash);
  4098. if (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode})) {
  4099. return $hmccu_hash->{hmccu}{dp}{$devtype}{spc}{$mode};
  4100. }
  4101. else {
  4102. return '';
  4103. }
  4104. }
  4105. ######################################################################
  4106. # Check if datapoint is valid.
  4107. # Parameter chn can be a channel address or a channel number. If dpt
  4108. # contains a channel number parameter chn should be set to undef.
  4109. # Parameter dpt can contain a channel number.
  4110. # Parameter oper specifies access flag:
  4111. # 1 = datapoint readable
  4112. # 2 = datapoint writeable
  4113. # Return 1 if ccuflags is set to dptnocheck or datapoint is valid.
  4114. # Otherwise 0.
  4115. ######################################################################
  4116. sub HMCCU_IsValidDatapoint ($$$$$)
  4117. {
  4118. my ($hash, $devtype, $chn, $dpt, $oper) = @_;
  4119. my $fnc = "IsValidDatapoint";
  4120. my $hmccu_hash = HMCCU_GetHash ($hash);
  4121. return 0 if (!defined ($hmccu_hash));
  4122. if ($hash->{TYPE} eq 'HMCCU' && !defined ($devtype)) {
  4123. $devtype = HMCCU_GetDeviceType ($hmccu_hash, $chn, 'null');
  4124. }
  4125. return 1 if (HMCCU_IsFlag ($hmccu_hash->{NAME}, "dptnocheck"));
  4126. return 1 if (!exists ($hmccu_hash->{hmccu}{dp}));
  4127. my $chnno;
  4128. if (defined ($chn)) {
  4129. if ($chn =~ /^[0-9]{1,2}$/) {
  4130. $chnno = $chn;
  4131. }
  4132. elsif (HMCCU_IsValidChannel ($hmccu_hash, $chn, $HMCCU_FL_ADDRESS)) {
  4133. my ($a, $c) = split(":",$chn);
  4134. $chnno = $c;
  4135. }
  4136. else {
  4137. HMCCU_Trace ($hash, 2, $fnc, "$chn is not a valid channel address or number");
  4138. return 0;
  4139. }
  4140. }
  4141. elsif ($dpt =~ /^([0-9]{1,2})\.(.+)$/) {
  4142. $chnno = $1;
  4143. $dpt = $2;
  4144. }
  4145. else {
  4146. HMCCU_Trace ($hash, 2, $fnc, "channel number missing in datapoint $dpt");
  4147. return 0;
  4148. }
  4149. my $v = (exists ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}) &&
  4150. ($hmccu_hash->{hmccu}{dp}{$devtype}{ch}{$chnno}{$dpt}{oper} & $oper)) ? 1 : 0;
  4151. HMCCU_Trace ($hash, 2, $fnc, "devtype=$devtype, chnno=$chnno, dpt=$dpt, valid=$v");
  4152. return $v;
  4153. }
  4154. ######################################################################
  4155. # Get list of device or channel addresses for which device or channel
  4156. # name matches regular expression.
  4157. # Parameter mode can be 'dev' or 'chn'.
  4158. # Return number of matching entries.
  4159. ######################################################################
  4160. sub HMCCU_GetMatchingDevices ($$$$)
  4161. {
  4162. my ($hash, $regexp, $mode, $listref) = @_;
  4163. my $c = 0;
  4164. foreach my $name (sort keys %{$hash->{hmccu}{adr}}) {
  4165. next if ($name !~/$regexp/ || $hash->{hmccu}{adr}{$name}{addtype} ne $mode ||
  4166. $hash->{hmccu}{adr}{$name}{valid} == 0);
  4167. push (@$listref, $hash->{hmccu}{adr}{$name}{address});
  4168. $c++;
  4169. }
  4170. return $c;
  4171. }
  4172. ######################################################################
  4173. # Get name of a CCU device by address.
  4174. # Channel number will be removed if specified.
  4175. ######################################################################
  4176. sub HMCCU_GetDeviceName ($$$)
  4177. {
  4178. my ($hash, $addr, $default) = @_;
  4179. if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) {
  4180. $addr =~ s/:[0-9]+$//;
  4181. return $hash->{hmccu}{dev}{$addr}{name};
  4182. }
  4183. return $default;
  4184. }
  4185. ######################################################################
  4186. # Get name of a CCU device channel by address.
  4187. ######################################################################
  4188. sub HMCCU_GetChannelName ($$$)
  4189. {
  4190. my ($hash, $addr, $default) = @_;
  4191. if (HMCCU_IsValidChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) {
  4192. return $hash->{hmccu}{dev}{$addr}{name};
  4193. }
  4194. return $default;
  4195. }
  4196. ######################################################################
  4197. # Get type of a CCU device by address.
  4198. # Channel number will be removed if specified.
  4199. ######################################################################
  4200. sub HMCCU_GetDeviceType ($$$)
  4201. {
  4202. my ($hash, $addr, $default) = @_;
  4203. if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) {
  4204. $addr =~ s/:[0-9]+$//;
  4205. return $hash->{hmccu}{dev}{$addr}{type};
  4206. }
  4207. return $default;
  4208. }
  4209. ######################################################################
  4210. # Get number of channels of a CCU device.
  4211. # Channel number will be removed if specified.
  4212. ######################################################################
  4213. sub HMCCU_GetDeviceChannels ($$$)
  4214. {
  4215. my ($hash, $addr, $default) = @_;
  4216. if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) {
  4217. $addr =~ s/:[0-9]+$//;
  4218. return $hash->{hmccu}{dev}{$addr}{channels};
  4219. }
  4220. return 0;
  4221. }
  4222. ######################################################################
  4223. # Get interface of a CCU device by address.
  4224. # Channel number will be removed if specified.
  4225. ######################################################################
  4226. sub HMCCU_GetDeviceInterface ($$$)
  4227. {
  4228. my ($hash, $addr, $default) = @_;
  4229. if (HMCCU_IsValidDeviceOrChannel ($hash, $addr, $HMCCU_FL_ADDRESS)) {
  4230. $addr =~ s/:[0-9]+$//;
  4231. return $hash->{hmccu}{dev}{$addr}{interface};
  4232. }
  4233. return $default;
  4234. }
  4235. ######################################################################
  4236. # Get address of a CCU device or channel by CCU name or FHEM device
  4237. # name defined via HMCCUCHN or HMCCUDEV. FHEM device names must be
  4238. # preceded by "hmccu:". CCU names can be preceded by "ccu:".
  4239. # Return array with device address and channel no. If name is not
  4240. # found or refers to a device the specified default values will be
  4241. # returned.
  4242. ######################################################################
  4243. sub HMCCU_GetAddress ($$$$)
  4244. {
  4245. my ($hash, $name, $defadd, $defchn) = @_;
  4246. my $add = $defadd;
  4247. my $chn = $defchn;
  4248. my $chnno = $defchn;
  4249. my $addr = '';
  4250. my $type = '';
  4251. if ($name =~ /^hmccu:.+$/) {
  4252. # Name is a FHEM device name
  4253. $name =~ s/^hmccu://;
  4254. if ($name =~ /^([^:]+):([0-9]{1,2})$/) {
  4255. $name = $1;
  4256. $chnno = $2;
  4257. }
  4258. return ($defadd, $defchn) if (!exists ($defs{$name}));
  4259. my $dh = $defs{$name};
  4260. return ($defadd, $defchn) if ($dh->{TYPE} ne 'HMCCUCHN' && $dh->{TYPE} ne 'HMCCUDEV');
  4261. ($add, $chn) = HMCCU_SplitChnAddr ($dh->{ccuaddr});
  4262. $chn = $chnno if ($chn eq '');
  4263. return ($add, $chn);
  4264. }
  4265. elsif ($name =~ /^ccu:.+$/) {
  4266. # Name is a CCU device or channel name
  4267. $name =~ s/^ccu://;
  4268. }
  4269. if (exists ($hash->{hmccu}{adr}{$name})) {
  4270. # Name known by HMCCU
  4271. $addr = $hash->{hmccu}{adr}{$name}{address};
  4272. $type = $hash->{hmccu}{adr}{$name}{addtype};
  4273. }
  4274. elsif (exists ($hash->{hmccu}{dev}{$name})) {
  4275. # Address known by HMCCU
  4276. $addr = $name;
  4277. $type = $hash->{hmccu}{dev}{$name}{addtype};
  4278. }
  4279. else {
  4280. # Address not known. Query CCU
  4281. my ($dc, $cc) = HMCCU_GetDevice ($hash, $name);
  4282. if ($dc > 0 && $cc > 0 && exists ($hash->{hmccu}{adr}{$name})) {
  4283. $addr = $hash->{hmccu}{adr}{$name}{address};
  4284. $type = $hash->{hmccu}{adr}{$name}{addtype};
  4285. }
  4286. }
  4287. if ($addr ne '') {
  4288. if ($type eq 'chn') {
  4289. ($add, $chn) = split (":", $addr);
  4290. }
  4291. else {
  4292. $add = $addr;
  4293. }
  4294. }
  4295. return ($add, $chn);
  4296. }
  4297. ######################################################################
  4298. # Get addresses of group member devices.
  4299. # Group 'virtual' is ignored.
  4300. # Return list of device addresses or empty list on error.
  4301. ######################################################################
  4302. sub HMCCU_GetGroupMembers ($$)
  4303. {
  4304. my ($hash, $group) = @_;
  4305. return $group ne 'virtual' && exists ($hash->{hmccu}{grp}{$group}) ?
  4306. split (',', $hash->{hmccu}{grp}{$group}{devs}) : ();
  4307. }
  4308. ######################################################################
  4309. # Check if parameter is a channel address (syntax)
  4310. # f=1: Interface required.
  4311. ######################################################################
  4312. sub HMCCU_IsChnAddr ($$)
  4313. {
  4314. my ($id, $f) = @_;
  4315. if ($f) {
  4316. return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ ||
  4317. $id =~ /^.+\.[0-9A-F]{12,14}:[0-9]{1,2}$/ ||
  4318. $id =~ /^.+\.OL-.+:[0-9]{1,2}$/ ||
  4319. $id =~ /^.+\.BidCoS-RF:[0-9]{1,2}$/) ? 1 : 0;
  4320. }
  4321. else {
  4322. return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}:[0-9]{1,2}$/ ||
  4323. $id =~ /^[0-9A-F]{12,14}:[0-9]{1,2}$/ ||
  4324. $id =~ /^OL-.+:[0-9]{1,2}$/ ||
  4325. $id =~ /^BidCoS-RF:[0-9]{1,2}$/) ? 1 : 0;
  4326. }
  4327. }
  4328. ######################################################################
  4329. # Check if parameter is a device address (syntax)
  4330. # f=1: Interface required.
  4331. ######################################################################
  4332. sub HMCCU_IsDevAddr ($$)
  4333. {
  4334. my ($id, $f) = @_;
  4335. if ($f) {
  4336. return ($id =~ /^.+\.[\*]*[A-Z]{3}[0-9]{7}$/ ||
  4337. $id =~ /^.+\.[0-9A-F]{12,14}$/ ||
  4338. $id =~ /^.+\.OL-.+$/ ||
  4339. $id =~ /^.+\.BidCoS-RF$/) ? 1 : 0;
  4340. }
  4341. else {
  4342. return ($id =~ /^[\*]*[A-Z]{3}[0-9]{7}$/ ||
  4343. $id =~ /^[0-9A-F]{12,14}$/ ||
  4344. $id =~ /^OL-.+$/ ||
  4345. $id eq 'BidCoS-RF') ? 1 : 0;
  4346. }
  4347. }
  4348. ######################################################################
  4349. # Split channel address into device address and channel number.
  4350. # Returns device address only if parameter is already a device address.
  4351. ######################################################################
  4352. sub HMCCU_SplitChnAddr ($)
  4353. {
  4354. my ($addr) = @_;
  4355. my ($dev, $chn) = split (':', $addr);
  4356. $chn = '' if (!defined ($chn));
  4357. return ($dev, $chn);
  4358. }
  4359. ######################################################################
  4360. # Get list of client devices matching the specified criteria.
  4361. # If no criteria is specified all device names will be returned.
  4362. # Parameters modexp and namexp are regular expressions for module
  4363. # name and device name. Parameter internal contains a comma separated
  4364. # list of expressions like internal=valueexp.
  4365. # All parameters can be undefined. In this case all devices will be
  4366. # returned.
  4367. ######################################################################
  4368. sub HMCCU_FindClientDevices ($$$$)
  4369. {
  4370. my ($hash, $modexp, $namexp, $internal) = @_;
  4371. my @devlist = ();
  4372. foreach my $d (keys %defs) {
  4373. my $ch = $defs{$d};
  4374. my $m = 1;
  4375. next if (!defined ($ch->{TYPE}) || !defined ($ch->{NAME}));
  4376. next if (defined ($modexp) && $ch->{TYPE} !~ /$modexp/);
  4377. next if (defined ($namexp) && $ch->{NAME} !~ /$namexp/);
  4378. next if (defined ($hash) && exists ($ch->{IODev}) && $ch->{IODev} != $hash);
  4379. if (defined ($internal)) {
  4380. foreach my $intspec (split (',', $internal)) {
  4381. my ($i, $v) = split ('=', $intspec);
  4382. if (defined ($v) && exists ($ch->{$i}) && $ch->{$i} !~ /$v/) {
  4383. $m = 0;
  4384. last;
  4385. }
  4386. }
  4387. }
  4388. push @devlist, $ch->{NAME} if ($m == 1);
  4389. }
  4390. return @devlist;
  4391. }
  4392. ######################################################################
  4393. # Get name of assigned client device of type HMCCURPC or HMCCURPCPROC.
  4394. # Create a RPC device of type HMCCURPC or HMCCURPCPROC if none is
  4395. # found and parameter create is set to 1.
  4396. # Return (devname, create).
  4397. # Return empty string for devname if RPC device cannot be identified
  4398. # or created. Return create = 1 if device has been created and
  4399. # configuration should be saved.
  4400. ######################################################################
  4401. sub HMCCU_GetRPCDevice ($$$)
  4402. {
  4403. my ($hash, $create, $ifname) = @_;
  4404. my $name = $hash->{NAME};
  4405. my $rpcdevname;
  4406. my $rpcdevtype = 'HMCCURPCPROC';
  4407. my $rpchost = $hash->{host};
  4408. my $ccuflags = HMCCU_GetFlags ($name);
  4409. if ($ccuflags =~ /(procrpc|extrpc)/) {
  4410. return (HMCCU_Log ($hash, 1, "Interface not defined for RPC server of type HMCCURPCPROC", ''), 0)
  4411. if (!defined ($ifname));
  4412. $rpcdevname = HMCCU_GetRPCServerInfo ($hash, $ifname, 'device');
  4413. $rpchost = HMCCU_GetRPCServerInfo ($hash, $ifname, 'host');
  4414. return ($rpcdevname, 0) if (defined ($rpcdevname));
  4415. return ('', 0) if (!defined ($rpchost));
  4416. # $rpcdevtype = 'HMCCURPCPROC';
  4417. }
  4418. # elsif ($ccuflags =~ /extrpc/) {
  4419. # if (defined ($hash->{RPCDEV})) {
  4420. # if (exists ($defs{$hash->{RPCDEV}})) {
  4421. # my $rpchash = $defs{$hash->{RPCDEV}};
  4422. # return (HMCCU_Log ($hash, 1, "RPC device ".$hash->{RPCDEV}." is not assigned to $name", ''), 0)
  4423. # if (!defined ($rpchash->{IODev}) || $rpchash->{IODev} != $hash);
  4424. # }
  4425. # else {
  4426. # return (HMCCU_Log ($hash, 1, "RPC device ".$hash->{RPCDEV}." not found", ''), 0);
  4427. # }
  4428. # return $hash->{RPCDEV};
  4429. # }
  4430. # }
  4431. else {
  4432. return (HMCCU_Log ($hash, 1, "No need for RPC device when using internal RPC server", ''), 0);
  4433. }
  4434. # Search for RPC devices associated with I/O device
  4435. my @devlist;
  4436. foreach my $dev (keys %defs) {
  4437. my $devhash = $defs{$dev};
  4438. next if ($devhash->{TYPE} ne $rpcdevtype);
  4439. my $ip = 'null';
  4440. my $addrnum = inet_aton ($devhash->{host});
  4441. $ip = inet_ntoa ($addrnum) if (defined ($addrnum));
  4442. next if ($devhash->{host} ne $rpchost && $ip ne $rpchost);
  4443. # next if ($rpcdevtype eq 'HMCCURPCPROC' && $devhash->{rpcinterface} ne $ifname);
  4444. next if ($devhash->{rpcinterface} ne $ifname);
  4445. push @devlist, $devhash->{NAME};
  4446. }
  4447. my $devcnt = scalar (@devlist);
  4448. if ($devcnt == 1) {
  4449. # if ($ccuflags =~ /extrpc/) {
  4450. # $hash->{RPCDEV} = $devlist[0];
  4451. # }
  4452. # else {
  4453. $hash->{hmccu}{interfaces}{$ifname}{device} = $devlist[0];
  4454. # }
  4455. return ($devlist[0], 0);
  4456. }
  4457. elsif ($devcnt > 1) {
  4458. return (HMCCU_Log ($hash, 2, "Found more than one RPC device for interface $ifname", ''), 0);
  4459. }
  4460. HMCCU_Log ($hash, 1, "No RPC device defined for interface $ifname", undef);
  4461. # Create RPC device
  4462. if ($create) {
  4463. my $alias = "CCU RPC";
  4464. my $rpccreate = "d_rpc $rpcdevtype ".$hash->{host};
  4465. $rpcdevname = "d_rpc";
  4466. if (defined ($ifname)) {
  4467. $rpcdevname = makeDeviceName ("d_rpc".$ifname);
  4468. $alias .= " $ifname";
  4469. $rpccreate = "$rpcdevname $rpcdevtype $rpchost $ifname";
  4470. }
  4471. HMCCU_Log ($hash, 1, "Creating new RPC device $rpcdevname", undef);
  4472. my $ret = CommandDefine (undef, $rpccreate);
  4473. if (!defined ($ret)) {
  4474. # RPC device created. Set/copy some attributes from HMCCU device
  4475. my %rpcdevattr = ('room' => 'copy', 'group' => 'copy', 'icon' => 'copy',
  4476. 'stateFormat' => 'rpcstate/state', 'eventMap' => '/rpcserver on:on/rpcserver off:off/',
  4477. 'verbose' => 2, 'alias' => $alias );
  4478. foreach my $a (keys %rpcdevattr) {
  4479. my $v = $rpcdevattr{$a} eq 'copy' ? AttrVal ($name, $a, '') : $rpcdevattr{$a};
  4480. CommandAttr (undef, "$rpcdevname $a $v") if ($v ne '');
  4481. }
  4482. # $hash->{RPCDEV} = $rpcdevname if ($ccuflags =~ /extrpc/);
  4483. return ($rpcdevname, 1);
  4484. }
  4485. else {
  4486. Log3 $name, 1, "HMCCU: [$name] Definition of RPC device failed.";
  4487. Log3 $name, 1, "HMCCU: [$name] $ret";
  4488. }
  4489. }
  4490. return ('', 0);
  4491. }
  4492. ######################################################################
  4493. # Assign IO device to client device.
  4494. # Wrapper function for AssignIOPort()
  4495. # Parameter $hash refers to a client device of type HMCCURPCPROC,
  4496. # HMCCUDEV or HMCCUCHN.
  4497. # Parameters ioname and ifname are optional.
  4498. # Return 1 on success or 0 on error.
  4499. ######################################################################
  4500. sub HMCCU_AssignIODevice ($$$)
  4501. {
  4502. my ($hash, $ioname, $ifname) = @_;
  4503. my $type = $hash->{TYPE};
  4504. my $name = $hash->{NAME};
  4505. my $hmccu_hash;
  4506. AssignIoPort ($hash, $ioname);
  4507. $hmccu_hash = $hash->{IODev} if (exists ($hash->{IODev}));
  4508. if (!defined ($hmccu_hash) || !exists ($hmccu_hash->{TYPE}) || $hmccu_hash->{TYPE} ne 'HMCCU') {
  4509. Log3 $name, 1, "$type: [$name] Can't assign I/O device";
  4510. return 0;
  4511. }
  4512. if ($type eq 'HMCCURPCPROC' && defined ($ifname)) {
  4513. # Register RPC device
  4514. $hmccu_hash->{hmccu}{interfaces}{$ifname}{device} = $name;
  4515. }
  4516. return 1;
  4517. }
  4518. ######################################################################
  4519. # Get hash of HMCCU IO device which is responsible for device or
  4520. # channel specified by parameter. If param is undef the first device
  4521. # of type HMCCU will be returned.
  4522. ######################################################################
  4523. sub HMCCU_FindIODevice ($)
  4524. {
  4525. my ($param) = @_;
  4526. foreach my $dn (sort keys %defs) {
  4527. my $ch = $defs{$dn};
  4528. next if (!exists ($ch->{TYPE}));
  4529. next if ($ch->{TYPE} ne 'HMCCU');
  4530. my $disabled = AttrVal ($ch->{NAME}, 'disable', 0);
  4531. next if ($disabled);
  4532. return $ch if (!defined ($param));
  4533. return $ch if (HMCCU_IsValidDeviceOrChannel ($ch, $param, $HMCCU_FL_ALL));
  4534. }
  4535. return undef;
  4536. }
  4537. ######################################################################
  4538. # Get states of IO devices
  4539. ######################################################################
  4540. sub HMCCU_IODeviceStates ()
  4541. {
  4542. my $active = 0;
  4543. my $inactive = 0;
  4544. # Search for first HMCCU device
  4545. foreach my $dn (sort keys %defs) {
  4546. my $ch = $defs{$dn};
  4547. next if (!exists ($ch->{TYPE}));
  4548. next if ($ch->{TYPE} ne 'HMCCU');
  4549. if (exists ($ch->{ccustate}) && $ch->{ccustate} eq 'active') {
  4550. $active++;
  4551. }
  4552. else {
  4553. $inactive++;
  4554. }
  4555. }
  4556. return ($active, $inactive);
  4557. }
  4558. ######################################################################
  4559. # Get hash of HMCCU IO device. Useful for client devices. Accepts hash
  4560. # of HMCCU, HMCCUDEV or HMCCUCHN device as parameter.
  4561. # If hash is 0 or undefined the hash of the first device of type HMCCU
  4562. # will be returned.
  4563. ######################################################################
  4564. sub HMCCU_GetHash ($@)
  4565. {
  4566. my ($hash) = @_;
  4567. if (defined ($hash) && $hash != 0) {
  4568. if ($hash->{TYPE} eq 'HMCCUDEV' || $hash->{TYPE} eq 'HMCCUCHN') {
  4569. return $hash->{IODev} if (exists ($hash->{IODev}));
  4570. return HMCCU_FindIODevice ($hash->{ccuaddr}) if (exists ($hash->{ccuaddr}));
  4571. }
  4572. elsif ($hash->{TYPE} eq 'HMCCU') {
  4573. return $hash;
  4574. }
  4575. }
  4576. # Search for first HMCCU device
  4577. foreach my $dn (sort keys %defs) {
  4578. my $ch = $defs{$dn};
  4579. next if (!exists ($ch->{TYPE}));
  4580. return $ch if ($ch->{TYPE} eq 'HMCCU');
  4581. }
  4582. return undef;
  4583. }
  4584. ######################################################################
  4585. # Get attribute of client device. Fallback to attribute of IO device.
  4586. ######################################################################
  4587. sub HMCCU_GetAttribute ($$$$)
  4588. {
  4589. my ($hmccu_hash, $cl_hash, $attr_name, $attr_def) = @_;
  4590. my $value = AttrVal ($cl_hash->{NAME}, $attr_name, '');
  4591. $value = AttrVal ($hmccu_hash->{NAME}, $attr_name, $attr_def) if ($value eq '');
  4592. return $value;
  4593. }
  4594. ######################################################################
  4595. # Get number of occurrences of datapoint.
  4596. # Return 0 if datapoint does not exist.
  4597. ######################################################################
  4598. sub HMCCU_GetDatapointCount ($$$)
  4599. {
  4600. my ($hash, $devtype, $dpt) = @_;
  4601. if (exists ($hash->{hmccu}{dp}{$devtype}{cnt}{$dpt})) {
  4602. return $hash->{hmccu}{dp}{$devtype}{cnt}{$dpt};
  4603. }
  4604. else {
  4605. return 0;
  4606. }
  4607. }
  4608. ######################################################################
  4609. # Get channels and datapoints from attributes statechannel,
  4610. # statedatapoint and controldatapoint.
  4611. # Return attribute values. Attribute controldatapoint is splitted into
  4612. # controlchannel and datapoint name. If attribute statedatapoint
  4613. # contains channel number it is splitted into statechannel and
  4614. # datapoint name.
  4615. # If controldatapoint is not specified it will synchronized with
  4616. # statedatapoint.
  4617. ######################################################################
  4618. sub HMCCU_GetSpecialDatapoints ($$$$$)
  4619. {
  4620. # my ($hash, $defsc, $defsd, $defcc, $defcd) = @_;
  4621. my ($hash, $sc, $sd, $cc, $cd) = @_;
  4622. my $name = $hash->{NAME};
  4623. my $type = $hash->{TYPE};
  4624. my $statedatapoint = AttrVal ($name, 'statedatapoint', '');
  4625. my $statechannel = AttrVal ($name, 'statechannel', '');
  4626. my $controldatapoint = AttrVal ($name, 'controldatapoint', $statedatapoint);
  4627. if ($statedatapoint ne '') {
  4628. if ($statedatapoint =~ /^([0-9]+)\.(.+)$/) {
  4629. ($sc, $sd) = ($1, $2);
  4630. }
  4631. else {
  4632. $sd = $statedatapoint;
  4633. }
  4634. }
  4635. $sc = $statechannel if ($statechannel ne '' && $sc eq '');
  4636. if ($controldatapoint ne '') {
  4637. if ($controldatapoint =~ /^([0-9]+)\.(.+)$/) {
  4638. ($cc, $cd) = ($1, $2);
  4639. }
  4640. else {
  4641. $cd = $controldatapoint;
  4642. }
  4643. }
  4644. # For devices of type HMCCUCHN extract channel numbers from CCU device address
  4645. if ($type eq 'HMCCUCHN') {
  4646. $sc = $hash->{ccuaddr};
  4647. $sc =~ s/^[\*]*[0-9A-Z]+://;
  4648. $cc = $sc;
  4649. }
  4650. # Try to find state channel
  4651. my $c = -1;
  4652. if ($sc eq '' && $sd ne '') {
  4653. $c = HMCCU_FindDatapoint ($hash, $hash->{ccutype}, -1, $sd, 3);
  4654. $sc = $c if ($c >= 0);
  4655. }
  4656. # Try to find control channel
  4657. if ($cc eq '' && $cd ne '') {
  4658. $c = HMCCU_FindDatapoint ($hash, $hash->{ccutype}, -1, $cd, 3);
  4659. $cc = $c if ($c >= 0);
  4660. }
  4661. # By default set control channel and datapoint to state channel and datapoint
  4662. $cc = $sc if ($cc eq '');
  4663. $cd = $sd if ($cd eq '');
  4664. return ($sc, $sd, $cc, $cd);
  4665. }
  4666. ######################################################################
  4667. # Get attribute ccuflags.
  4668. # Default value is 'null'. With version 4.2.009 flag extrpc is
  4669. # substituted by procrpc.
  4670. ######################################################################
  4671. sub HMCCU_GetFlags ($)
  4672. {
  4673. my ($name) = @_;
  4674. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  4675. $ccuflags =~ s/extrpc/procrpc/g;
  4676. return $ccuflags;
  4677. }
  4678. ######################################################################
  4679. # Check if specific CCU flag is set.
  4680. ######################################################################
  4681. sub HMCCU_IsFlag ($$)
  4682. {
  4683. my ($name, $flag) = @_;
  4684. my $ccuflags = AttrVal ($name, 'ccuflags', 'null');
  4685. return $ccuflags =~ /$flag/ ? 1 : 0;
  4686. }
  4687. ######################################################################
  4688. # Get reading format considering default attribute
  4689. # ccudef-readingformat defined in I/O device.
  4690. # Default reading format for virtual groups is always 'name'.
  4691. ######################################################################
  4692. sub HMCCU_GetAttrReadingFormat ($$)
  4693. {
  4694. my ($clhash, $iohash) = @_;
  4695. my $clname = $clhash->{NAME};
  4696. my $ioname = $iohash->{NAME};
  4697. my $rfdef = '';
  4698. if (exists ($clhash->{ccutype}) && $clhash->{ccutype} =~ /^HM-CC-VG/) {
  4699. $rfdef = 'name';
  4700. }
  4701. else {
  4702. $rfdef = AttrVal ($ioname, 'ccudef-readingformat', 'datapoint');
  4703. }
  4704. return AttrVal ($clname, 'ccureadingformat', $rfdef);
  4705. }
  4706. ######################################################################
  4707. # Get attributes substitute and substexcl considering default
  4708. # attribute ccudef-substitute defined in I/O device.
  4709. # Substitute ${xxx} by datapoint value.
  4710. ######################################################################
  4711. sub HMCCU_GetAttrSubstitute ($$)
  4712. {
  4713. my ($clhash, $iohash) = @_;
  4714. my $fnc = "GetAttrSubstitute";
  4715. my $clname = $clhash->{NAME};
  4716. my $ioname = $iohash->{NAME};
  4717. my $substdef = AttrVal ($ioname, 'ccudef-substitute', '');
  4718. my $subst = AttrVal ($clname, 'substitute', $substdef);
  4719. $subst .= ";$substdef" if ($subst ne $substdef && $substdef ne '');
  4720. HMCCU_Trace ($clhash, 2, $fnc, "subst = $subst");
  4721. return $subst if ($subst !~ /\$\{.+\}/);
  4722. $subst = HMCCU_SubstVariables ($clhash, $subst, undef);
  4723. HMCCU_Trace ($clhash, 2, $fnc, "subst_vars = $subst");
  4724. return $subst;
  4725. }
  4726. ######################################################################
  4727. # Clear RPC queue
  4728. ######################################################################
  4729. sub HMCCU_ResetRPCQueue ($$)
  4730. {
  4731. my ($hash, $port) = @_;
  4732. my $name = $hash->{NAME};
  4733. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  4734. my $clkey = 'CB'.$port;
  4735. if (HMCCU_QueueOpen ($hash, $rpcqueue."_".$port."_".$hash->{CCUNum})) {
  4736. HMCCU_QueueReset ($hash);
  4737. while (defined (HMCCU_QueueDeq ($hash))) { }
  4738. HMCCU_QueueClose ($hash);
  4739. }
  4740. $hash->{hmccu}{rpc}{$clkey}{queue} = '' if (exists ($hash->{hmccu}{rpc}{$clkey}{queue}));
  4741. }
  4742. ######################################################################
  4743. # Process RPC server event
  4744. ######################################################################
  4745. sub HMCCU_ProcessEvent ($$)
  4746. {
  4747. my ($hash, $event) = @_;
  4748. my $name = $hash->{NAME};
  4749. my $rh = \%{$hash->{hmccu}{rpc}};
  4750. return undef if (!defined ($event) || $event eq '');
  4751. my @t = split (/\|/, $event);
  4752. my $tc = scalar (@t);
  4753. # Update statistic counters
  4754. if (exists ($hash->{hmccu}{ev}{$t[0]})) {
  4755. $hash->{hmccu}{evtime} = time ();
  4756. $hash->{hmccu}{ev}{total}++;
  4757. $hash->{hmccu}{ev}{$t[0]}++;
  4758. $hash->{hmccu}{evtimeout} = 0 if ($hash->{hmccu}{evtimeout} == 1);
  4759. }
  4760. else {
  4761. my $errtok = $t[0];
  4762. $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  4763. return HMCCU_Log ($hash, 2, "Received unknown event from CCU: ".$errtok, undef);
  4764. }
  4765. # Check event syntax
  4766. return HMCCU_Log ($hash, 2, "Wrong number of parameters in event $event", undef)
  4767. if (exists ($rpceventargs{$t[0]}) && ($tc-1) != $rpceventargs{$t[0]});
  4768. if ($t[0] eq 'EV') {
  4769. #
  4770. # Update of datapoint
  4771. # Input: EV|Adress|Datapoint|Value
  4772. # Output: EV, DevAdd, ChnNo, Reading='', Value
  4773. #
  4774. return HMCCU_Log ($hash, 2, "Invalid channel ".$t[1], undef)
  4775. if (!HMCCU_IsValidChannel ($hash, $t[1], $HMCCU_FL_ADDRESS));
  4776. my ($add, $chn) = split (/:/, $t[1]);
  4777. return ($t[0], $add, $chn, $t[2], $t[3]);
  4778. }
  4779. elsif ($t[0] eq 'SL') {
  4780. #
  4781. # RPC server enters server loop
  4782. # Input: SL|Pid|Servername
  4783. # Output: SL, Servername, Pid
  4784. #
  4785. my $clkey = $t[2];
  4786. return HMCCU_Log ($hash, 0, "Received SL event for unknown RPC server $clkey", undef)
  4787. if (!exists ($rh->{$clkey}));
  4788. Log3 $name, 0, "HMCCU: Received SL event. RPC server $clkey enters server loop";
  4789. $rh->{$clkey}{loop} = 1 if ($rh->{$clkey}{pid} == $t[1]);
  4790. return ($t[0], $clkey, $t[1]);
  4791. }
  4792. elsif ($t[0] eq 'IN') {
  4793. #
  4794. # RPC server initialized
  4795. # Input: IN|INIT|State|Servername
  4796. # Output: IN, Servername, Running, NotRunning, ClientsUpdated, UpdateErrors
  4797. #
  4798. my $clkey = $t[3];
  4799. my $norun = 0;
  4800. my $run = 0;
  4801. return HMCCU_Log ($hash, 0, "Received IN event for unknown RPC server $clkey", undef)
  4802. if (!exists ($rh->{$clkey}));
  4803. Log3 $name, 0, "HMCCU: Received IN event. RPC server $clkey initialized.";
  4804. $rh->{$clkey}{state} = $rh->{$clkey}{pid} != 0 ? "running" : "initialized";
  4805. # Check if all RPC servers were initialized. Set overall status
  4806. foreach my $ser (keys %{$rh}) {
  4807. $norun++ if ($rh->{$ser}{state} ne "running" && $rh->{$ser}{pid} != 0);
  4808. $norun++ if ($rh->{$ser}{state} ne "initialized" && $rh->{$ser}{pid} == 0);
  4809. $run++ if ($rh->{$ser}{state} eq "running");
  4810. }
  4811. HMCCU_SetRPCState ($hash, 'running') if ($norun == 0);
  4812. $hash->{hmccu}{rpcinit} = $run;
  4813. return ($t[0], $clkey, $run, $norun);
  4814. }
  4815. elsif ($t[0] eq 'EX') {
  4816. #
  4817. # RPC server shutdown
  4818. # Input: EX|SHUTDOWN|Pid|Servername
  4819. # Output: EX, Servername, Pid, Flag, Run
  4820. #
  4821. my $clkey = $t[3];
  4822. my $run = 0;
  4823. return HMCCU_Log ($hash, 0, "Received EX event for unknown RPC server $clkey", undef)
  4824. if (!exists ($rh->{$clkey}));
  4825. Log3 $name, 0, "HMCCU: Received EX event. RPC server $clkey terminated.";
  4826. my $f = $hash->{RPCState} eq "restarting" ? 2 : 1;
  4827. delete $rh->{$clkey};
  4828. # Check if all RPC servers were terminated. Set overall status
  4829. foreach my $ser (keys %{$rh}) {
  4830. $run++ if ($rh->{$ser}{state} ne "inactive");
  4831. }
  4832. if ($run == 0) {
  4833. HMCCU_SetRPCState ($hash, 'inactive') if ($f == 1);
  4834. $hash->{RPCPID} = '0';
  4835. }
  4836. $hash->{hmccu}{rpccount} = $run;
  4837. $hash->{hmccu}{rpcinit} = $run;
  4838. return ($t[0], $clkey, $t[2], $f, $run);
  4839. }
  4840. elsif ($t[0] eq 'ND') {
  4841. #
  4842. # CCU device added
  4843. # Input: ND|C/D|Address|Type|Version|Firmware|RxMode
  4844. # Output: ND, DevAdd, C/D, Type, Version, Firmware, RxMode
  4845. #
  4846. return ($t[0], $t[2], $t[1], $t[3], $t[4], $t[5], $t[6]);
  4847. }
  4848. elsif ($t[0] eq 'DD' || $t[0] eq 'RA') {
  4849. #
  4850. # CCU device added, deleted or readded
  4851. # Input: {DD,RA}|Address
  4852. # Output: {DD,RA}, DevAdd
  4853. #
  4854. return ($t[0], $t[1]);
  4855. }
  4856. elsif ($t[0] eq 'UD') {
  4857. #
  4858. # CCU device updated
  4859. # Input: UD|Address|Hint
  4860. # Output: UD, DevAdd, Hint
  4861. #
  4862. return ($t[0], $t[1], $t[2]);
  4863. }
  4864. elsif ($t[0] eq 'RD') {
  4865. #
  4866. # CCU device replaced
  4867. # Input: RD|Address1|Address2
  4868. # Output: RD, Address1, Address2
  4869. #
  4870. return ($t[0], $t[1], $t[2]);
  4871. }
  4872. elsif ($t[0] eq 'ST') {
  4873. #
  4874. # Statistic data. Store snapshots of sent and received events.
  4875. # Input: ST|nTotal|nEV|nND|nDD|nRD|nRA|nUD|nIN|nSL|nEX
  4876. # Output: ST, ...
  4877. #
  4878. my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX');
  4879. for (my $i=0; $i<10; $i++) {
  4880. $hash->{hmccu}{evs}{$stkeys[$i]} = $t[$i+1];
  4881. $hash->{hmccu}{evr}{$stkeys[$i]} = $hash->{hmccu}{ev}{$stkeys[$i]};
  4882. }
  4883. return @t;
  4884. }
  4885. else {
  4886. my $errtok = $t[0];
  4887. $errtok =~ s/([\x00-\xFF])/sprintf("0x%X ",ord($1))/eg;
  4888. Log3 $name, 2, "HMCCU: Received unknown event from CCU: ".$errtok;
  4889. }
  4890. return undef;
  4891. }
  4892. ######################################################################
  4893. # Timer function for reading RPC queue
  4894. ######################################################################
  4895. sub HMCCU_ReadRPCQueue ($)
  4896. {
  4897. my ($hash) = @_;
  4898. my $name = $hash->{NAME};
  4899. my $eventno = 0;
  4900. my $f = 0;
  4901. my @newdevices;
  4902. my @deldevices;
  4903. my @termpids;
  4904. my $newcount = 0;
  4905. my $devcount = 0;
  4906. my %events = ();
  4907. my %devices = ();
  4908. my $ccuflags = HMCCU_GetFlags ($name);
  4909. my $rpcinterval = AttrVal ($name, 'rpcinterval', 5);
  4910. my $rpcqueue = AttrVal ($name, 'rpcqueue', '/tmp/ccuqueue');
  4911. my $rpcevtimeout = AttrVal ($name, 'rpcevtimeout', $HMCCU_TIMEOUT_EVENT);
  4912. my $maxevents = $rpcinterval*10;
  4913. $maxevents = 50 if ($maxevents > 50);
  4914. $maxevents = 10 if ($maxevents < 10);
  4915. my @portlist = HMCCU_GetRPCPortList ($hash);
  4916. foreach my $port (@portlist) {
  4917. my $clkey = 'CB'.$port;
  4918. next if (!exists ($hash->{hmccu}{rpc}{$clkey}{queue}));
  4919. my $queuename = $hash->{hmccu}{rpc}{$clkey}{queue};
  4920. next if ($queuename eq '');
  4921. if (!HMCCU_QueueOpen ($hash, $queuename)) {
  4922. Log3 $name, 1, "HMCCU: Can't open file queue $queuename";
  4923. next;
  4924. }
  4925. my $element = HMCCU_QueueDeq ($hash);
  4926. while (defined ($element)) {
  4927. Log3 $name, 2, "HMCCU: Event = $element" if ($ccuflags =~ /logEvents/);
  4928. my ($et, @par) = HMCCU_ProcessEvent ($hash, $element);
  4929. if (defined ($et)) {
  4930. if ($et eq 'EV') {
  4931. $events{$par[0]}{$par[1]}{$par[2]} = $par[3];
  4932. $eventno++;
  4933. last if ($eventno == $maxevents);
  4934. }
  4935. elsif ($et eq 'ND') {
  4936. # push (@newdevices, $par[1]);
  4937. $newcount++ if (!exists ($hash->{hmccu}{dev}{$par[0]}));
  4938. # $hash->{hmccu}{dev}{$par[1]}{chntype} = $par[3];
  4939. $devices{$par[0]}{flag} = 'N';
  4940. $devices{$par[0]}{version} = $par[3];
  4941. if ($par[1] eq 'D') {
  4942. $devices{$par[0]}{addtype} = 'dev';
  4943. $devices{$par[0]}{type} = $par[2];
  4944. $devices{$par[0]}{firmware} = $par[4];
  4945. $devices{$par[0]}{rxmode} = $par[5];
  4946. }
  4947. else {
  4948. $devices{$par[0]}{addtype} = 'chn';
  4949. $devices{$par[0]}{usetype} = $par[2];
  4950. }
  4951. $devcount++;
  4952. }
  4953. elsif ($et eq 'DD') {
  4954. # push (@deldevices, $par[0]);
  4955. $devices{$par[0]}{flag} = 'D';
  4956. $devcount++;
  4957. # $delcount++;
  4958. }
  4959. elsif ($et eq 'RD') {
  4960. $devices{$par[0]}{flag} = 'R';
  4961. $devices{$par[0]}{newaddr} = $par[1];
  4962. $devcount++;
  4963. }
  4964. elsif ($et eq 'SL') {
  4965. InternalTimer (gettimeofday()+$HMCCU_INIT_INTERVAL1,
  4966. 'HMCCU_RPCRegisterCallback', $hash, 0);
  4967. $f = -1;
  4968. last;
  4969. }
  4970. elsif ($et eq 'EX') {
  4971. push (@termpids, $par[1]);
  4972. $f = $par[2];
  4973. last;
  4974. }
  4975. }
  4976. last if ($f == -1);
  4977. # Read next element from queue
  4978. $element = HMCCU_QueueDeq ($hash);
  4979. }
  4980. HMCCU_QueueClose ($hash);
  4981. }
  4982. # Update readings
  4983. HMCCU_UpdateMultipleDevices ($hash, \%events) if ($eventno > 0);
  4984. # Update device table and client device parameter
  4985. HMCCU_UpdateDeviceTable ($hash, \%devices) if ($devcount > 0);
  4986. return if ($f == -1);
  4987. # Check if events from CCU timed out
  4988. if ($hash->{hmccu}{evtime} > 0 && time()-$hash->{hmccu}{evtime} > $rpcevtimeout &&
  4989. $hash->{hmccu}{evtimeout} == 0) {
  4990. $hash->{hmccu}{evtimeout} = 1;
  4991. $hash->{ccustate} = HMCCU_TCPConnect ($hash->{host}, 8181) ne '' ? 'timeout' : 'unreachable';
  4992. Log3 $name, 2, "HMCCU: Received no events from CCU since $rpcevtimeout seconds";
  4993. DoTrigger ($name, "No events from CCU since $rpcevtimeout seconds");
  4994. }
  4995. else {
  4996. $hash->{ccustate} = 'active' if ($hash->{ccustate} ne 'active');
  4997. }
  4998. my @hm_pids;
  4999. my @hm_tids;
  5000. HMCCU_IsRPCServerRunning ($hash, \@hm_pids, \@hm_tids);
  5001. my $nhm_pids = scalar (@hm_pids);
  5002. my $nhm_tids = scalar (@hm_tids);
  5003. Log3 $name, 1, "HMCCU: Externally launched RPC server(s) detected. f=$f" if ($nhm_tids > 0);
  5004. if ($f > 0) {
  5005. # At least one RPC server has been stopped. Update PID list
  5006. $hash->{RPCPID} = $nhm_pids > 0 ? join(',',@hm_pids) : '0';
  5007. Log3 $name, 0, "HMCCU: RPC server(s) with PID(s) ".join(',',@termpids)." shut down. f=$f";
  5008. # Output statistic counters
  5009. foreach my $cnt (sort keys %{$hash->{hmccu}{ev}}) {
  5010. Log3 $name, 3, "HMCCU: Eventcount $cnt = ".$hash->{hmccu}{ev}{$cnt};
  5011. }
  5012. }
  5013. if ($f == 2 && $nhm_pids == 0) {
  5014. # All RPC servers terminated and restart flag set
  5015. if ($ccuflags !~ /(extrpc|procrpc)/) {
  5016. return if (HMCCU_StartIntRPCServer ($hash));
  5017. }
  5018. Log3 $name, 0, "HMCCU: Restart of RPC server failed";
  5019. }
  5020. if ($nhm_pids > 0) {
  5021. # Reschedule reading of RPC queues if at least one RPC server is running
  5022. InternalTimer (gettimeofday()+$rpcinterval, 'HMCCU_ReadRPCQueue', $hash, 0);
  5023. }
  5024. else {
  5025. # No more RPC servers active
  5026. Log3 $name, 0, "HMCCU: Periodical check found no RPC Servers";
  5027. # Deregister existing callbacks
  5028. HMCCU_RPCDeRegisterCallback ($hash);
  5029. # Cleanup hash variables
  5030. my @clkeylist = keys %{$hash->{hmccu}{rpc}};
  5031. foreach my $clkey (@clkeylist) {
  5032. delete $hash->{hmccu}{rpc}{$clkey};
  5033. }
  5034. $hash->{hmccu}{rpccount} = 0;
  5035. $hash->{hmccu}{rpcinit} = 0;
  5036. $hash->{RPCPID} = '0';
  5037. $hash->{RPCPRC} = 'none';
  5038. HMCCU_SetRPCState ($hash, 'inactive');
  5039. Log3 $name, 0, "HMCCU: All RPC servers stopped";
  5040. DoTrigger ($name, "All RPC servers stopped");
  5041. }
  5042. }
  5043. ######################################################################
  5044. # Execute Homematic command on CCU.
  5045. # If parameter mode is 1 an empty string is a valid result.
  5046. ######################################################################
  5047. sub HMCCU_HMCommand ($$$)
  5048. {
  5049. my ($cl_hash, $cmd, $mode) = @_;
  5050. my $cl_name = $cl_hash->{NAME};
  5051. my $fnc = "HMCommand";
  5052. my $io_hash = HMCCU_GetHash ($cl_hash);
  5053. my $ccureqtimeout = AttrVal ($io_hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5054. my $url = "http://".$io_hash->{host}.":8181/tclrega.exe";
  5055. my $value;
  5056. HMCCU_Trace ($cl_hash, 2, $fnc, "URL=$url, cmd=$cmd");
  5057. my $response = GetFileFromURL ($url, $ccureqtimeout, $cmd);
  5058. if (defined ($response)) {
  5059. $value = $response;
  5060. $value =~ s/<xml>(.*)<\/xml>//;
  5061. $value =~ s/\r//g;
  5062. HMCCU_Trace ($cl_hash, 2, $fnc, "Response=$response, Value=".(defined ($value) ? $value : "undef"));
  5063. }
  5064. else {
  5065. HMCCU_Trace ($cl_hash, 2, $fnc, "Response=undef");
  5066. }
  5067. if ($mode == 1) {
  5068. return (defined ($value) && $value ne 'null') ? $value : undef;
  5069. }
  5070. else {
  5071. return (defined ($value) && $value ne '' && $value ne 'null') ? $value : undef;
  5072. }
  5073. }
  5074. ######################################################################
  5075. # Execute Homematic command on CCU without waiting for response.
  5076. ######################################################################
  5077. sub HMCCU_HMCommandNB ($$$)
  5078. {
  5079. my ($cl_hash, $cmd, $cbfunc) = @_;
  5080. my $cl_name = $cl_hash->{NAME};
  5081. my $fnc = "HMCommandNB";
  5082. my $io_hash = HMCCU_GetHash ($cl_hash);
  5083. my $ccureqtimeout = AttrVal ($io_hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5084. my $url = "http://".$io_hash->{host}.":8181/tclrega.exe";
  5085. HMCCU_Trace ($cl_hash, 2, $fnc, "URL=$url");
  5086. if (defined ($cbfunc)) {
  5087. my $param = { url => $url, timeout => $ccureqtimeout, data => $cmd, method => "POST",
  5088. callback => $cbfunc, devhash => $cl_hash };
  5089. HttpUtils_NonblockingGet ($param);
  5090. }
  5091. else {
  5092. my $param = { url => $url, timeout => $ccureqtimeout, data => $cmd, method => "POST",
  5093. callback => \&HMCCU_HMCommandCB, devhash => $cl_hash };
  5094. HttpUtils_NonblockingGet ($param);
  5095. }
  5096. }
  5097. ######################################################################
  5098. # Default callback function for non blocking CCU request.
  5099. ######################################################################
  5100. sub HMCCU_HMCommandCB ($$$)
  5101. {
  5102. my ($param, $err, $data) = @_;
  5103. my $hash = $param->{devhash};
  5104. my $fnc = "HMCommandCB";
  5105. HMCCU_Log ($hash, 2, "Error during CCU request. $err", undef) if ($err ne '');
  5106. HMCCU_Trace ($hash, 2, $fnc, "URL=".$param->{url}."<br>Response=$data");
  5107. }
  5108. ######################################################################
  5109. # Execute Homematic script on CCU.
  5110. # Parameters: device-hash, script-code or script-name, parameter-hash
  5111. # If content of hmscript starts with a ! the following text is treated
  5112. # as name of an internal HomeMatic script function defined in
  5113. # HMCCUConf.pm.
  5114. # If content of hmscript is enclosed in [] the content is treated as
  5115. # HomeMatic script code. Characters [] will be removed.
  5116. # Otherwise hmscript is the name of a file containing Homematic script
  5117. # code.
  5118. # Return script output or error message starting with "ERROR:".
  5119. ######################################################################
  5120. sub HMCCU_HMScriptExt ($$$)
  5121. {
  5122. my ($hash, $hmscript, $params) = @_;
  5123. my $name = $hash->{NAME};
  5124. my $code = $hmscript;
  5125. my $scrname = '';
  5126. return HMCCU_LogError ($hash, 2, "CCU host name not defined") if (!exists ($hash->{host}));
  5127. my $host = $hash->{host};
  5128. if ($hmscript =~ /^!(.*)$/) {
  5129. # Internal script
  5130. $scrname = $1;
  5131. return "ERROR: Can't find internal script $scrname" if (!exists ($HMCCU_SCRIPTS->{$scrname}));
  5132. $code = $HMCCU_SCRIPTS->{$scrname}{code};
  5133. }
  5134. elsif ($hmscript =~ /^\[(.*)\]$/) {
  5135. # Script code
  5136. $code = $1;
  5137. }
  5138. else {
  5139. # Script file
  5140. if (open (SCRFILE, "<$hmscript")) {
  5141. my @lines = <SCRFILE>;
  5142. $code = join ("\n", @lines);
  5143. close (SCRFILE);
  5144. }
  5145. else {
  5146. return "ERROR: Can't open script file";
  5147. }
  5148. }
  5149. # Check and replace variables
  5150. if (defined ($params)) {
  5151. my @parnames = keys %{$params};
  5152. if ($scrname ne '') {
  5153. if (scalar (@parnames) != $HMCCU_SCRIPTS->{$scrname}{parameters}) {
  5154. return "ERROR: Wrong number of parameters. Usage: $scrname ".
  5155. $HMCCU_SCRIPTS->{$scrname}{syntax};
  5156. }
  5157. foreach my $p (split (/[, ]+/, $HMCCU_SCRIPTS->{$scrname}{syntax})) {
  5158. return "ERROR: Missing definition of parameter $p" if (!exists ($params->{$p}));
  5159. }
  5160. }
  5161. foreach my $svar (keys %{$params}) {
  5162. next if ($code !~ /\$$svar/);
  5163. $code =~ s/\$$svar/$params->{$svar}/g;
  5164. }
  5165. }
  5166. else {
  5167. if ($scrname ne '' && $HMCCU_SCRIPTS->{$scrname}{parameters} > 0) {
  5168. return "ERROR: Wrong number of parameters. Usage: $scrname ".
  5169. $HMCCU_SCRIPTS->{$scrname}{syntax};
  5170. }
  5171. }
  5172. # Execute script on CCU
  5173. my $url = "http://".$host.":8181/tclrega.exe";
  5174. my $ua = new LWP::UserAgent ();
  5175. my $response = $ua->post($url, Content => $code);
  5176. if ($response->is_success ()) {
  5177. my $output = $response->content;
  5178. $output =~ s/<xml>.*<\/xml>//;
  5179. $output =~ s/\r//g;
  5180. return $output;
  5181. }
  5182. else {
  5183. my $msg = $response->status_line();
  5184. Log3 $name, 2, "HMCCU: HMScript failed. $msg";
  5185. return "ERROR: HMScript failed. $msg";
  5186. }
  5187. }
  5188. ######################################################################
  5189. # Bulk update of reading considering attribute substexcl.
  5190. ######################################################################
  5191. sub HMCCU_BulkUpdate ($$$$)
  5192. {
  5193. my ($hash, $reading, $orgval, $subval) = @_;
  5194. my $excl = AttrVal ($hash->{NAME}, 'substexcl', '');
  5195. readingsBulkUpdate ($hash, $reading, ($excl ne '' && $reading =~ /$excl/ ? $orgval : $subval));
  5196. }
  5197. ######################################################################
  5198. # Get datapoint value from CCU and update reading.
  5199. # If parameter noupd is defined and > 0 no readings will be updated.
  5200. ######################################################################
  5201. sub HMCCU_GetDatapoint ($@)
  5202. {
  5203. my ($cl_hash, $param, $noupd) = @_;
  5204. my $cl_name = $cl_hash->{NAME};
  5205. my $fnc = "GetDatapoint";
  5206. my $value = '';
  5207. my $io_hash = HMCCU_GetHash ($cl_hash);
  5208. return (-3, $value) if (!defined ($io_hash));
  5209. return (-4, $value) if ($cl_hash->{TYPE} ne 'HMCCU' && $cl_hash->{ccudevstate} eq 'deleted');
  5210. my $readingformat = HMCCU_GetAttrReadingFormat ($cl_hash, $io_hash);
  5211. my $substitute = HMCCU_GetAttrSubstitute ($cl_hash, $io_hash);
  5212. my ($statechn, $statedpt, $controlchn, $controldpt) = HMCCU_GetSpecialDatapoints (
  5213. $cl_hash, '', 'STATE', '', '');
  5214. my $ccuget = HMCCU_GetAttribute ($io_hash, $cl_hash, 'ccuget', 'Value');
  5215. my $ccureqtimeout = AttrVal ($io_hash->{NAME}, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5216. my $cmd = '';
  5217. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($io_hash, $param,
  5218. $HMCCU_FLAG_INTERFACE);
  5219. return (-1, $value) if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD);
  5220. if ($flags == $HMCCU_FLAGS_IACD) {
  5221. $cmd = 'Write((datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).'.$ccuget.'())';
  5222. }
  5223. elsif ($flags == $HMCCU_FLAGS_NCD) {
  5224. $cmd = 'Write((dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").'.$ccuget.'())';
  5225. ($add, $chn) = HMCCU_GetAddress ($io_hash, $nam, '', '');
  5226. }
  5227. HMCCU_Trace ($cl_hash, 2, $fnc, "CMD=$cmd, param=$param, ccuget=$ccuget");
  5228. $value = HMCCU_HMCommand ($cl_hash, $cmd, 1);
  5229. if (defined ($value) && $value ne '' && $value ne 'null') {
  5230. if (!defined ($noupd) || $noupd == 0) {
  5231. $value = HMCCU_UpdateSingleDatapoint ($cl_hash, $chn, $dpt, $value);
  5232. }
  5233. else {
  5234. my $svalue = HMCCU_ScaleValue ($cl_hash, $chn, $dpt, $value, 0);
  5235. $value = HMCCU_Substitute ($svalue, $substitute, 0, $chn, $dpt);
  5236. }
  5237. HMCCU_Trace ($cl_hash, 2, $fnc, "Value of $chn.$dpt = $value");
  5238. return (1, $value);
  5239. }
  5240. else {
  5241. HMCCU_Log ($cl_hash, 1, "Error CMD = $cmd", 0);
  5242. return (-2, '');
  5243. }
  5244. }
  5245. ######################################################################
  5246. # Set multiple datapoints on CCU in a single request.
  5247. # Parameter params is a hash reference. Keys are full qualified CCU
  5248. # datapoint specifications in format:
  5249. # no.interface.address:channelno.datapoint
  5250. # Parameter no defines the command order.
  5251. ######################################################################
  5252. sub HMCCU_SetMultipleDatapoints ($$) {
  5253. my ($cl_hash, $params) = @_;
  5254. my $fnc = "SetMultipleDatapoints";
  5255. my $type = $cl_hash->{TYPE};
  5256. my $io_hash = HMCCU_GetHash ($cl_hash);
  5257. return -3 if (!defined ($io_hash));
  5258. return -4 if (exists ($cl_hash->{ccudevstate}) && $cl_hash->{ccudevstate} eq 'deleted');
  5259. my $io_name = $io_hash->{NAME};
  5260. my $cl_name = $cl_hash->{NAME};
  5261. my $ccureqtimeout = AttrVal ($io_name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5262. my $ccuflags = HMCCU_GetFlags ($io_name);
  5263. my $readingformat = HMCCU_GetAttrReadingFormat ($cl_hash, $io_hash);
  5264. my $ccuverify = AttrVal ($cl_name, 'ccuverify', 0);
  5265. my $ccuchange = AttrVal ($cl_name, 'ccuSetOnChange', 'null');
  5266. my $ccutype = $cl_hash->{ccutype};
  5267. # Build Homematic script
  5268. my @addrlist = $cl_hash->{ccuif} eq 'fhem' ? split (',', $cl_hash->{ccugroup}) : ($cl_hash->{ccuaddr});
  5269. my $cmd = '';
  5270. foreach my $a (@addrlist) {
  5271. foreach my $p (sort keys %$params) {
  5272. my $v = $params->{$p};
  5273. HMCCU_Trace ($cl_hash, 2, $fnc, "dpt=$p, value=$v");
  5274. # Check address
  5275. my ($no, $int, $addchn, $dpt) = split (/\./, $p);
  5276. return -1 if (!defined ($dpt));
  5277. my ($add, $chn) = split (/:/, $addchn);
  5278. return -1 if (!defined ($chn));
  5279. return -8 if (!HMCCU_IsValidDatapoint ($cl_hash, $ccutype, $chn, $dpt, 2));
  5280. # Override device interface and address if device is virtual
  5281. if ($cl_hash->{ccuif} eq 'fhem') {
  5282. $add = $a;
  5283. $int = HMCCU_GetDeviceInterface ($io_hash, $add, '');
  5284. return -20 if ($int eq '');
  5285. }
  5286. if ($ccutype eq 'HM-Dis-EP-WM55' && $dpt eq 'SUBMIT') {
  5287. $v = HMCCU_EncodeEPDisplay ($v);
  5288. }
  5289. else {
  5290. $v = HMCCU_ScaleValue ($cl_hash, $chn, $dpt, $v, 1);
  5291. }
  5292. my $dpttype = HMCCU_GetDatapointAttr ($io_hash, $ccutype, $chn, $dpt, 'type');
  5293. $v = "'".$v."'" if (defined ($dpttype) && $dpttype == $HMCCU_TYPE_STRING);
  5294. my $c = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$v.");\n";
  5295. if ($dpt =~ /$ccuchange/) {
  5296. $cmd .= 'if((datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).Value() != '.$v.") {\n$c}\n";
  5297. }
  5298. else {
  5299. $cmd .= $c;
  5300. }
  5301. }
  5302. }
  5303. # Execute command (non blocking)
  5304. if ($ccuflags =~ /nonBlocking/) {
  5305. HMCCU_HMCommandNB ($cl_hash, $cmd, undef);
  5306. return 0;
  5307. }
  5308. # Execute command (blocking)
  5309. my $response = HMCCU_HMCommand ($cl_hash, $cmd, 1);
  5310. return -2 if (!defined ($response));
  5311. # Datapoint verification ???
  5312. return 0;
  5313. }
  5314. ######################################################################
  5315. # Set datapoint on CCU.
  5316. # Parameter param is a valid CCU or FHEM datapoint specification:
  5317. # [ccu:]address:channelnumber.datapoint
  5318. # [ccu:]channelname.datapoint
  5319. # hmccu:hmccudev_name.channelnumber.datapoint
  5320. # hmccu:hmccuchn_name.datapoint
  5321. ######################################################################
  5322. sub HMCCU_SetDatapoint ($$$)
  5323. {
  5324. my ($hash, $param, $value) = @_;
  5325. my $fnc = "SetDatapoint";
  5326. my $type = $hash->{TYPE};
  5327. my $hmccu_hash = HMCCU_GetHash ($hash);
  5328. return -3 if (!defined ($hmccu_hash));
  5329. return -4 if (exists ($hash->{ccudevstate}) && $hash->{ccudevstate} eq 'deleted');
  5330. my $name = $hmccu_hash->{NAME};
  5331. my $cdname = $hash->{NAME};
  5332. my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5333. my $ccuflags = HMCCU_GetFlags ($name);
  5334. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash);
  5335. my $ccuverify = AttrVal ($cdname, 'ccuverify', 0);
  5336. HMCCU_Trace ($hash, 2, $fnc, "param=$param, value=$value");
  5337. if ($param =~ /^hmccu:.+$/) {
  5338. my @t = split (/\./, $param);
  5339. return -1 if (scalar (@t) < 2 || scalar (@t) > 3);
  5340. my $fhdpt = pop @t;
  5341. my ($fhadd, $fhchn) = HMCCU_GetAddress ($hmccu_hash, $t[0], '', '');
  5342. $fhchn = $t[1] if (scalar (@t) == 2);
  5343. return -1 if ($fhadd eq '' || $fhchn eq '');
  5344. $param = "$fhadd:$fhchn.$fhdpt";
  5345. }
  5346. elsif ($param =~ /^ccu:(.+)$/) {
  5347. $param = $1;
  5348. }
  5349. my $cmd = '';
  5350. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  5351. $HMCCU_FLAG_INTERFACE);
  5352. return -1 if ($flags != $HMCCU_FLAGS_IACD && $flags != $HMCCU_FLAGS_NCD);
  5353. if ($hash->{ccutype} eq 'HM-Dis-EP-WM55' && $dpt eq 'SUBMIT') {
  5354. $value = HMCCU_EncodeEPDisplay ($value);
  5355. }
  5356. else {
  5357. $value = HMCCU_ScaleValue ($hash, $chn, $dpt, $value, 1);
  5358. }
  5359. my $dpttype = HMCCU_GetDatapointAttr ($hmccu_hash, $hash->{ccutype}, $chn, $dpt, 'type');
  5360. if (defined ($dpttype) && $dpttype == $HMCCU_TYPE_STRING) {
  5361. $value = "'".$value."'";
  5362. }
  5363. if ($flags == $HMCCU_FLAGS_IACD) {
  5364. # $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')';
  5365. $nam = HMCCU_GetChannelName ($hmccu_hash, $add.":".$chn, '');
  5366. }
  5367. elsif ($flags == $HMCCU_FLAGS_NCD) {
  5368. # $cmd = '(dom.GetObject(ID_CHANNELS)).Get("'.$nam.'").DPByHssDP("'.$dpt.'").State('.$value.')';
  5369. ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $nam, '', '');
  5370. }
  5371. if ($type eq 'HMCCUDEV' && $hash->{ccuif} eq 'fhem' && $hash->{ccutype} ne 'n/a' && exists ($hash->{ccugroup})) {
  5372. for my $gaddr (split (',', $hash->{ccugroup})) {
  5373. $cmd .= '(datapoints.Get("'.$int.'.'.$gaddr.':'.$chn.'.'.$dpt.'")).State('.$value.");\n";
  5374. }
  5375. }
  5376. else {
  5377. $cmd = '(datapoints.Get("'.$int.'.'.$add.':'.$chn.'.'.$dpt.'")).State('.$value.')';
  5378. }
  5379. my $addr = $add.":".$chn;
  5380. if ($ccuflags =~ /nonBlocking/) {
  5381. HMCCU_HMCommandNB ($hash, $cmd, undef);
  5382. return 0;
  5383. }
  5384. # Execute command (blocking)
  5385. my $response = HMCCU_HMCommand ($hash, $cmd, 1);
  5386. HMCCU_Trace ($hash, 2, $fnc,
  5387. "Addr=$addr Name=$nam<br>".
  5388. "Script response = \n".(defined ($response) ? $response: 'undef')."<br>".
  5389. "Script = \n".$cmd);
  5390. return -2 if (!defined ($response));
  5391. # Verify setting of datapoint value or update reading with new datapoint value
  5392. if (HMCCU_IsValidDatapoint ($hash, $hash->{ccutype}, $addr, $dpt, 1)) {
  5393. if ($ccuverify == 1) {
  5394. my ($rc, $result) = HMCCU_GetDatapoint ($hash, $param, 0);
  5395. return $rc;
  5396. }
  5397. elsif ($ccuverify == 2) {
  5398. HMCCU_UpdateSingleDatapoint ($hash, $chn, $dpt, $value);
  5399. }
  5400. }
  5401. return 0;
  5402. }
  5403. ######################################################################
  5404. # Scale, spread and/or shift datapoint value.
  5405. # Mode: 0 = Get/Divide, 1 = Set/Multiply
  5406. # Supports reversing of value if value range is specified. Syntax for
  5407. # Rule is:
  5408. # [ChannelNo.]Datapoint:Factor
  5409. # [!][ChannelNo.]Datapoint:Min:Max:Range1:Range2
  5410. # If Datapoint name starts with a ! the value is reversed. In case of
  5411. # an error original value is returned.
  5412. ######################################################################
  5413. sub HMCCU_ScaleValue ($$$$$)
  5414. {
  5415. my ($hash, $chnno, $dpt, $value, $mode) = @_;
  5416. my $name = $hash->{NAME};
  5417. my $ccuscaleval = AttrVal ($name, 'ccuscaleval', '');
  5418. return $value if ($ccuscaleval eq '');
  5419. my @sl = split (',', $ccuscaleval);
  5420. foreach my $sr (@sl) {
  5421. my $f = 1.0;
  5422. my @a = split (':', $sr);
  5423. my $n = scalar (@a);
  5424. next if ($n != 2 && $n != 5);
  5425. my $rev = 0;
  5426. my $dn = $a[0];
  5427. my $cn = $chnno;
  5428. if ($dn =~ /^\!(.+)$/) {
  5429. # Invert
  5430. $dn = $1;
  5431. $rev = 1;
  5432. }
  5433. if ($dn =~ /^([0-9]{1,2})\.(.+)$/) {
  5434. # Compare channel number
  5435. $cn = $1;
  5436. $dn = $2;
  5437. }
  5438. next if ($dpt ne $dn || ($chnno ne '' && $cn ne $chnno));
  5439. if ($n == 2) {
  5440. $f = ($a[1] == 0.0) ? 1.0 : $a[1];
  5441. return ($mode == 0) ? $value/$f : $value*$f;
  5442. }
  5443. else {
  5444. # Do not scale if value out of range or interval wrong
  5445. return $value if ($a[1] > $a[2] || $a[3] > $a[4]);
  5446. return $value if ($mode == 0 && ($value < $a[1] || $value > $a[2]));
  5447. # return $value if ($mode == 1 && ($value >= $a[1] && $value <= $a[2]));
  5448. return $value if ($mode == 1 && ($value < $a[3] || $value > $a[4]));
  5449. # Reverse value
  5450. if ($rev) {
  5451. my $dr = ($mode == 0) ? $a[1]+$a[2] : $a[3]+$a[4];
  5452. $value = $dr-$value;
  5453. }
  5454. my $d1 = $a[2]-$a[1];
  5455. my $d2 = $a[4]-$a[3];
  5456. return $value if ($d1 == 0.0 || $d2 == 0.0);
  5457. $f = $d1/$d2;
  5458. return ($mode == 0) ? $value/$f+$a[3] : ($value-$a[3])*$f;
  5459. }
  5460. }
  5461. return $value;
  5462. }
  5463. ######################################################################
  5464. # Get CCU system variables and update readings.
  5465. # System variable readings are stored in I/O device. Unsupported
  5466. # characters in variable names are substituted.
  5467. ######################################################################
  5468. sub HMCCU_GetVariables ($$)
  5469. {
  5470. my ($hash, $pattern) = @_;
  5471. my $name = $hash->{NAME};
  5472. my $count = 0;
  5473. my $result = '';
  5474. my $ccureadings = AttrVal ($name, 'ccureadings', HMCCU_IsFlag ($name, "noReadings") ? 0 : 1);
  5475. my $response = HMCCU_HMScriptExt ($hash, "!GetVariables", undef);
  5476. return (-2, $response) if ($response eq '' || $response =~ /^ERROR:.*/);
  5477. readingsBeginUpdate ($hash) if ($ccureadings);
  5478. foreach my $vardef (split /\n/, $response) {
  5479. my @vardata = split /=/, $vardef;
  5480. next if (@vardata != 3);
  5481. next if ($vardata[0] !~ /$pattern/);
  5482. my $rn = HMCCU_CorrectName ($vardata[0]);
  5483. my $value = HMCCU_FormatReadingValue ($hash, $vardata[2], $vardata[0]);
  5484. readingsBulkUpdate ($hash, $rn, $value) if ($ccureadings);
  5485. $result .= $vardata[0].'='.$vardata[2]."\n";
  5486. $count++;
  5487. }
  5488. readingsEndUpdate ($hash, 1) if ($ccureadings);
  5489. return ($count, $result);
  5490. }
  5491. ######################################################################
  5492. # Timer function for periodic update of CCU system variables.
  5493. ######################################################################
  5494. sub HMCCU_UpdateVariables ($)
  5495. {
  5496. my ($hash) = @_;
  5497. if (exists ($hash->{hmccu}{ccuvarspat})) {
  5498. HMCCU_GetVariables ($hash, $hash->{hmccu}{ccuvarspat});
  5499. InternalTimer (gettimeofday ()+$hash->{hmccu}{ccuvarsint}, "HMCCU_UpdateVariables", $hash);
  5500. }
  5501. }
  5502. ######################################################################
  5503. # Set CCU system variable. If parameter vartype is undefined system
  5504. # variable must exist in CCU. Following variable types are supported:
  5505. # bool, list, number, text. Parameter params is a hash reference of
  5506. # script parameters.
  5507. # Return 0 on success, error code on error.
  5508. ######################################################################
  5509. sub HMCCU_SetVariable ($$$$$)
  5510. {
  5511. my ($hash, $varname, $value, $vartype, $params) = @_;
  5512. my $name = $hash->{NAME};
  5513. my $ccureqtimeout = AttrVal ($name, "ccuReqTimeout", $HMCCU_TIMEOUT_REQUEST);
  5514. my %varfnc = (
  5515. "bool" => "!CreateBoolVariable", "list", "!CreateListVariable",
  5516. "number" => "!CreateNumericVariable", "text", "!CreateStringVariable"
  5517. );
  5518. if (!defined ($vartype)) {
  5519. my $cmd = qq(dom.GetObject("$varname").State("$value"));
  5520. my $response = HMCCU_HMCommand ($hash, $cmd, 1);
  5521. return HMCCU_Log ($hash, 1, "CMD=$cmd", -2) if (!defined ($response));
  5522. }
  5523. else {
  5524. return -18 if (!exists ($varfnc{$vartype}));
  5525. # Set default values for variable attributes
  5526. $params->{name} = $varname if (!exists ($params->{name}));
  5527. $params->{init} = $value if (!exists ($params->{init}));
  5528. $params->{unit} = "" if (!exists ($params->{unit}));
  5529. $params->{desc} = "" if (!exists ($params->{desc}));
  5530. $params->{min} = "0" if ($vartype eq 'number' && !exists ($params->{min}));
  5531. $params->{max} = "65000" if ($vartype eq 'number' && !exists ($params->{max}));
  5532. $params->{list} = $value if ($vartype eq 'list' && !exists ($params->{list}));
  5533. $params->{valtrue} = "ist wahr" if ($vartype eq 'bool' && !exists ($params->{valtrue}));
  5534. $params->{valfalse} = "ist falsch" if ($vartype eq 'bool' && !exists ($params->{valfalse}));
  5535. my $rc = HMCCU_HMScriptExt ($hash, $varfnc{$vartype}, $params);
  5536. return HMCCU_Log ($hash, 1, $rc, -2) if ($rc =~ /^ERROR:.*/);
  5537. }
  5538. return 0;
  5539. }
  5540. ######################################################################
  5541. # Update all datapoints / readings of device or channel considering
  5542. # attribute ccureadingfilter.
  5543. # Parameter $ccuget can be 'State', 'Value' or 'Attr'.
  5544. ######################################################################
  5545. sub HMCCU_GetUpdate ($$$)
  5546. {
  5547. my ($cl_hash, $addr, $ccuget) = @_;
  5548. my $name = $cl_hash->{NAME};
  5549. my $type = $cl_hash->{TYPE};
  5550. my $fnc = "GetUpdate";
  5551. my $disable = AttrVal ($name, 'disable', 0);
  5552. return 1 if ($disable == 1);
  5553. my $hmccu_hash = HMCCU_GetHash ($cl_hash);
  5554. return -3 if (!defined ($hmccu_hash));
  5555. return -4 if ($type ne 'HMCCU' && $cl_hash->{ccudevstate} eq 'deleted');
  5556. my $nam = '';
  5557. my $list = '';
  5558. my $script = '';
  5559. $ccuget = HMCCU_GetAttribute ($hmccu_hash, $cl_hash, 'ccuget', 'Value') if ($ccuget eq 'Attr');
  5560. if (HMCCU_IsValidChannel ($hmccu_hash, $addr, $HMCCU_FL_ADDRESS)) {
  5561. $nam = HMCCU_GetChannelName ($hmccu_hash, $addr, '');
  5562. return -1 if ($nam eq '');
  5563. my ($stadd, $stchn) = split (':', $addr);
  5564. my $stnam = HMCCU_GetChannelName ($hmccu_hash, "$stadd:0", '');
  5565. $list = $stnam eq '' ? $nam : $stnam . "," . $nam;
  5566. $script = "!GetDatapointsByChannel";
  5567. }
  5568. elsif (HMCCU_IsValidDevice ($hmccu_hash, $addr, $HMCCU_FL_ADDRESS)) {
  5569. $nam = HMCCU_GetDeviceName ($hmccu_hash, $addr, '');
  5570. return -1 if ($nam eq '');
  5571. $list = $nam if ($cl_hash->{ccuif} ne 'fhem');
  5572. $script = "!GetDatapointsByDevice";
  5573. # Consider members of group device
  5574. if ($type eq 'HMCCUDEV' &&
  5575. ($cl_hash->{ccuif} eq 'VirtualDevices' || $cl_hash->{ccuif} eq 'fhem') &&
  5576. exists ($cl_hash->{ccugroup})) {
  5577. foreach my $gd (split (",", $cl_hash->{ccugroup})) {
  5578. $nam = HMCCU_GetDeviceName ($hmccu_hash, $gd, '');
  5579. $list .= ','.$nam if ($nam ne '');
  5580. }
  5581. }
  5582. }
  5583. else {
  5584. return -1;
  5585. }
  5586. my $response = HMCCU_HMScriptExt ($hmccu_hash, $script,
  5587. { list => $list, ccuget => $ccuget });
  5588. HMCCU_Trace ($cl_hash, 2, $fnc, "Addr=$addr Name=$nam Script=$script<br>".
  5589. "Script response = \n".$response);
  5590. return -2 if ($response eq '' || $response =~ /^ERROR:.*/);
  5591. my @dpdef = split /\n/, $response;
  5592. my $count = pop (@dpdef);
  5593. return -10 if (!defined ($count) || $count == 0);
  5594. my %events = ();
  5595. foreach my $dp (@dpdef) {
  5596. my ($chnname, $dpspec, $value) = split /=/, $dp;
  5597. next if (!defined ($value));
  5598. my ($iface, $chnadd, $dpt) = split /\./, $dpspec;
  5599. next if (!defined ($dpt));
  5600. my ($add, $chn) = ('', '');
  5601. if ($iface eq 'sysvar' && $chnadd eq 'link') {
  5602. ($add, $chn) = HMCCU_GetAddress ($hmccu_hash, $chnname, '', '');
  5603. }
  5604. else {
  5605. ($add, $chn) = HMCCU_SplitChnAddr ($chnadd);
  5606. }
  5607. next if ($chn eq '');
  5608. $events{$add}{$chn}{$dpt} = $value;
  5609. }
  5610. # if ($cl_hash->{ccuif} eq 'fhem') {
  5611. # # Calculate datapoints of virtual group device
  5612. # if ($cl_hash->{ccutype} ne 'n/a') {
  5613. # foreach my $da (split (",", $cl_hash->{ccugroup})) {
  5614. # foreach my $cn (keys %{$events{$da}}) {
  5615. # foreach my $dp (keys %{$events{$da}{$cn}}) {
  5616. # if (defined ($events{$da}{$cn}{$dp})) {
  5617. # $events{$cl_hash->{ccuaddr}}{$cn}{$dp} = $events{$da}{$cn}{$dp}
  5618. # }
  5619. # }
  5620. # }
  5621. # }
  5622. # }
  5623. # }
  5624. HMCCU_UpdateMultipleDevices ($hmccu_hash, \%events);
  5625. return 1;
  5626. }
  5627. ######################################################################
  5628. # Get multiple datapoints of channels and update readings of client
  5629. # devices.
  5630. # Returncodes: -1 = Invalid channel/datapoint in list
  5631. # -2 = CCU script execution failed
  5632. # -3 = Cannot detect IO device
  5633. # On success number of updated readings is returned.
  5634. ######################################################################
  5635. sub HMCCU_GetChannel ($$)
  5636. {
  5637. my ($hash, $chnref) = @_;
  5638. my $name = $hash->{NAME};
  5639. my $type = $hash->{TYPE};
  5640. my $count = 0;
  5641. my $chnlist = '';
  5642. my $result = '';
  5643. my %chnpars;
  5644. my %objects = ();
  5645. my $objcount = 0;
  5646. my $ccuget = AttrVal ($name, 'ccuget', 'Value');
  5647. # Build channel list. Datapoints and substitution rules are ignored.
  5648. foreach my $chndef (@$chnref) {
  5649. my ($channel, $substitute) = split /\s+/, $chndef;
  5650. next if (!defined ($channel) || $channel =~ /^#/ || $channel eq '');
  5651. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hash, $channel,
  5652. $HMCCU_FLAG_INTERFACE | $HMCCU_FLAG_DATAPOINT);
  5653. if ($flags == $HMCCU_FLAGS_IACD || $flags == $HMCCU_FLAGS_NCD) {
  5654. $nam = HMCCU_GetChannelName ($hash, $add.':'.$chn, '') if ($flags == $HMCCU_FLAGS_IACD);
  5655. $chnlist = $chnlist eq '' ? $nam : $chnlist.','.$nam;
  5656. $chnpars{$nam}{sub} = $substitute;
  5657. $chnpars{$nam}{dpt} = $dpt;
  5658. }
  5659. else {
  5660. return (-1, $result);
  5661. }
  5662. }
  5663. return (0, $result) if ($chnlist eq '');
  5664. my $response = HMCCU_HMScriptExt ($hash, "!GetChannel",
  5665. { list => $chnlist, ccuget => $ccuget });
  5666. return (-2, $result) if ($response eq '' || $response =~ /^ERROR:.*/);
  5667. # Output format is Channelname=Interface.Channeladdress.Datapoint=Value
  5668. foreach my $dpdef (split /\n/, $response) {
  5669. my ($chnname, $dptaddr, $value) = split /=/, $dpdef;
  5670. next if (!defined ($value));
  5671. my ($iface, $chnaddr, $dpt) = split /\./, $dptaddr;
  5672. next if (!defined ($dpt));
  5673. my ($add, $chn) = HMCCU_SplitChnAddr ($chnaddr);
  5674. next if ($chn eq '');
  5675. if (defined ($chnpars{$chnname}{dpt}) && $chnpars{$chnname}{dpt} ne '') {
  5676. next if ($dpt !~ $chnpars{$chnname}{dpt});
  5677. }
  5678. $value = HMCCU_Substitute ($value, $chnpars{$chnname}{sub}, 0, $chn, $dpt);
  5679. $objects{$add}{$chn}{$dpt} = $value;
  5680. $objcount++;
  5681. }
  5682. $count = HMCCU_UpdateMultipleDevices ($hash, \%objects) if ($objcount > 0);
  5683. return ($count, $result);
  5684. }
  5685. ######################################################################
  5686. # Get RPC paramSet or paramSetDescription
  5687. ######################################################################
  5688. sub HMCCU_RPCGetConfig ($$$$)
  5689. {
  5690. my ($hash, $param, $mode, $filter) = @_;
  5691. my $name = $hash->{NAME};
  5692. my $type = $hash->{TYPE};
  5693. my $fnc = "RPCGetConfig";
  5694. my $method = $mode eq 'listParamset' ? 'getParamset' : $mode;
  5695. my $addr;
  5696. my $result = '';
  5697. my $res = '';
  5698. my $hmccu_hash = HMCCU_GetHash ($hash);
  5699. return (-3, $result) if (!defined ($hmccu_hash));
  5700. return (-4, $result) if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted');
  5701. my $ccuflags = HMCCU_GetFlags ($name);
  5702. my $ccureadings = AttrVal ($name, 'ccureadings', $ccuflags =~ /noReadings/ ? 0 : 1);
  5703. my $readingformat = HMCCU_GetAttrReadingFormat ($hash, $hmccu_hash);
  5704. my $substitute = HMCCU_GetAttrSubstitute ($hash, $hmccu_hash);
  5705. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  5706. $HMCCU_FLAG_FULLADDR);
  5707. return (-1, '') if (!($flags & $HMCCU_FLAG_ADDRESS));
  5708. $addr = $add;
  5709. $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL);
  5710. my $rpctype = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'type');
  5711. my $port = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'port');
  5712. return (-9, '') if (!defined ($rpctype) || !defined ($port));
  5713. if ($rpctype eq 'B') {
  5714. # Search RPC device
  5715. my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0, $int);
  5716. return (-17, '') if ($rpcdev eq '');
  5717. HMCCU_Trace ($hash, 2, $fnc, "Method=$method Addr=$addr Port=$port");
  5718. if ($ccuflags =~ /(extrpc|procrpc)/) {
  5719. # $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, $method, $BINRPC_STRING, $addr,
  5720. # $BINRPC_STRING, "MASTER");
  5721. # }
  5722. # elsif ($ccuflags =~ /procrpc/) {
  5723. $res = HMCCURPCPROC_SendRequest ($defs{$rpcdev}, $method, $BINRPC_STRING, $addr,
  5724. $BINRPC_STRING, "MASTER");
  5725. }
  5726. }
  5727. else {
  5728. my $url = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'url');
  5729. return (-9, '') if (!defined ($url));
  5730. HMCCU_Trace ($hash, 2, $fnc, "Method=$method Addr=$addr Port=$port");
  5731. my $client = RPC::XML::Client->new ($url);
  5732. $res = $client->simple_request ($method, $addr, "MASTER");
  5733. }
  5734. return (-5, "Function not available") if (!defined ($res));
  5735. if (defined ($res)) {
  5736. HMCCU_Trace ($hash, 2, $fnc,
  5737. "Dump of RPC request $method $addr. Result type=".ref($res)."<br>".
  5738. HMCCU_RefToString ($res));
  5739. }
  5740. if (ref ($res) eq 'HASH') {
  5741. my $parcount = scalar (keys %$res);
  5742. if (exists ($res->{faultString})) {
  5743. Log3 $name, 1, "HMCCU: ".$res->{faultString};
  5744. return (-2, $res->{faultString});
  5745. }
  5746. elsif ($parcount == 0) {
  5747. return (-5, "CCU returned no data");
  5748. }
  5749. }
  5750. else {
  5751. return (-2, defined ($RPC::XML::ERROR) ? $RPC::XML::ERROR : '');
  5752. }
  5753. if ($mode eq 'getParamsetDescription') {
  5754. foreach my $key (sort keys %$res) {
  5755. my $oper = '';
  5756. $oper .= 'R' if ($res->{$key}->{OPERATIONS} & 1);
  5757. $oper .= 'W' if ($res->{$key}->{OPERATIONS} & 2);
  5758. $oper .= 'E' if ($res->{$key}->{OPERATIONS} & 4);
  5759. $result .= $key.": ".$res->{$key}->{TYPE}." [".$oper."]\n";
  5760. }
  5761. }
  5762. elsif ($mode eq 'listParamset') {
  5763. foreach my $key (sort keys %$res) {
  5764. next if ($key !~ /$filter/);
  5765. my $value = $res->{$key};
  5766. $result .= "$key=$value\n";
  5767. }
  5768. }
  5769. else {
  5770. readingsBeginUpdate ($hash) if ($ccureadings);
  5771. foreach my $key (sort keys %$res) {
  5772. next if ($key !~ /$filter/);
  5773. my $value = $res->{$key};
  5774. $result .= "$key=$value\n";
  5775. next if (!$ccureadings);
  5776. $value = HMCCU_FormatReadingValue ($hash, $value, $key);
  5777. $value = HMCCU_Substitute ($value, $substitute, 0, $chn, $key);
  5778. my @readings = HMCCU_GetReadingName ($hash, $int, $add, $chn, $key, $nam, $readingformat);
  5779. foreach my $rn (@readings) {
  5780. next if ($rn eq '');
  5781. $rn = "R-".$rn;
  5782. readingsBulkUpdate ($hash, $rn, $value);
  5783. }
  5784. }
  5785. readingsEndUpdate ($hash, 1) if ($ccureadings);
  5786. }
  5787. return (0, $result);
  5788. }
  5789. ######################################################################
  5790. # Set RPC paramSet
  5791. ######################################################################
  5792. sub HMCCU_RPCSetConfig ($$$)
  5793. {
  5794. my ($hash, $param, $parref) = @_;
  5795. my $name = $hash->{NAME};
  5796. my $type = $hash->{TYPE};
  5797. my $ccuflags = HMCCU_GetFlags ($name);
  5798. my $addr;
  5799. my $res;
  5800. my $hmccu_hash = HMCCU_GetHash ($hash);
  5801. return -3 if (!defined ($hmccu_hash));
  5802. return -4 if ($type ne 'HMCCU' && $hash->{ccudevstate} eq 'deleted');
  5803. my ($int, $add, $chn, $dpt, $nam, $flags) = HMCCU_ParseObject ($hmccu_hash, $param,
  5804. $HMCCU_FLAG_FULLADDR);
  5805. return -1 if (!($flags & $HMCCU_FLAG_ADDRESS));
  5806. $addr = $add;
  5807. $addr .= ':'.$chn if ($flags & $HMCCU_FLAG_CHANNEL);
  5808. my $rpctype = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'type');
  5809. return -9 if (!defined ($rpctype));
  5810. my $port = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'port');
  5811. return -9 if (!defined ($port));
  5812. if ($ccuflags =~ /trace/) {
  5813. my $ps = '';
  5814. foreach my $p (keys %$parref) {
  5815. $ps .= ", ".$p."=".$parref->{$p};
  5816. }
  5817. Log3 $name, 2, "HMCCU: RPCSetConfig: addr=$addr".$ps;
  5818. }
  5819. if ($rpctype eq 'B') {
  5820. # Search RPC device
  5821. my $rpcdev = HMCCU_GetRPCDevice ($hmccu_hash, 0, $int);
  5822. return -17 if ($rpcdev eq '');
  5823. # Rebuild parameter hash for binary encoding
  5824. my %binpar;
  5825. foreach my $e (keys %$parref) {
  5826. $binpar{$e}{T} = $BINRPC_STRING;
  5827. $binpar{$e}{V} = $parref->{$e};
  5828. }
  5829. if ($ccuflags =~ /(extrpc|procrpc)/) {
  5830. # $res = HMCCURPC_SendBinRequest ($defs{$rpcdev}, $port, "putParamset", $BINRPC_STRING, $addr,
  5831. # $BINRPC_STRING, "MASTER", $BINRPC_STRUCT, \%binpar);
  5832. # }
  5833. # elsif ($ccuflags =~ /procrpc/) {
  5834. $res = HMCCURPCPROC_SendRequest ($defs{$rpcdev}, "putParamset", $BINRPC_STRING, $addr,
  5835. $BINRPC_STRING, "MASTER", $BINRPC_STRUCT, \%binpar);
  5836. }
  5837. }
  5838. else {
  5839. my $url = HMCCU_GetRPCServerInfo ($hmccu_hash, $int, 'url');
  5840. return -9 if (!defined ($url));
  5841. my $client = RPC::XML::Client->new ($url);
  5842. $res = $client->simple_request ("putParamset", $addr, "MASTER", $parref);
  5843. }
  5844. return -5 if (! defined ($res));
  5845. return HMCCU_Log ($hash, 1, "HMCCU: RPC request failed. ".$res->{faultString}, -2)
  5846. if (ref ($res) && exists ($res->{faultString}));
  5847. return 0;
  5848. }
  5849. ######################################################################
  5850. # *** FILEQUEUE FUNCTIONS ***
  5851. ######################################################################
  5852. ######################################################################
  5853. # Open file queue
  5854. ######################################################################
  5855. sub HMCCU_QueueOpen ($$)
  5856. {
  5857. my ($hash, $queue_file) = @_;
  5858. my $idx_file = $queue_file . '.idx';
  5859. $queue_file .= '.dat';
  5860. my $mode = '0666';
  5861. umask (0);
  5862. $hash->{hmccu}{queue}{block_size} = 64;
  5863. $hash->{hmccu}{queue}{seperator} = "\n";
  5864. $hash->{hmccu}{queue}{sep_length} = length $hash->{hmccu}{queue}{seperator};
  5865. $hash->{hmccu}{queue}{queue_file} = $queue_file;
  5866. $hash->{hmccu}{queue}{idx_file} = $idx_file;
  5867. $hash->{hmccu}{queue}{queue} = new IO::File $queue_file, O_CREAT | O_RDWR, oct($mode) or return 0;
  5868. $hash->{hmccu}{queue}{idx} = new IO::File $idx_file, O_CREAT | O_RDWR, oct($mode) or return 0;
  5869. ### Default ptr to 0, replace it with value in idx file if one exists
  5870. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  5871. $hash->{hmccu}{queue}{idx}->sysread($hash->{hmccu}{queue}{ptr}, 1024);
  5872. $hash->{hmccu}{queue}{ptr} = '0' unless $hash->{hmccu}{queue}{ptr};
  5873. if($hash->{hmccu}{queue}{ptr} > -s $queue_file)
  5874. {
  5875. $hash->{hmccu}{queue}{idx}->truncate(0) or return 0;
  5876. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  5877. $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0;
  5878. }
  5879. return 1;
  5880. }
  5881. ######################################################################
  5882. # Close file queue
  5883. ######################################################################
  5884. sub HMCCU_QueueClose ($)
  5885. {
  5886. my ($hash) = @_;
  5887. if (exists ($hash->{hmccu}{queue})) {
  5888. $hash->{hmccu}{queue}{idx}->close();
  5889. $hash->{hmccu}{queue}{queue}->close();
  5890. delete $hash->{hmccu}{queue};
  5891. }
  5892. }
  5893. sub HMCCU_QueueReset ($)
  5894. {
  5895. my ($hash) = @_;
  5896. $hash->{hmccu}{queue}{idx}->truncate(0) or return 0;
  5897. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  5898. $hash->{hmccu}{queue}{idx}->syswrite('0') or return 0;
  5899. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET);
  5900. return 1;
  5901. }
  5902. ######################################################################
  5903. # Put value in file queue
  5904. ######################################################################
  5905. sub HMCCU_QueueEnq ($$)
  5906. {
  5907. my ($hash, $element) = @_;
  5908. return 0 if (!exists ($hash->{hmccu}{queue}));
  5909. $hash->{hmccu}{queue}{queue}->sysseek(0, SEEK_END);
  5910. $element =~ s/$hash->{hmccu}{queue}{seperator}//g;
  5911. $hash->{hmccu}{queue}{queue}->syswrite($element.$hash->{hmccu}{queue}{seperator}) or return 0;
  5912. return 1;
  5913. }
  5914. ######################################################################
  5915. # Return next value in file queue
  5916. ######################################################################
  5917. sub HMCCU_QueueDeq ($)
  5918. {
  5919. my ($hash) = @_;
  5920. my $name = $hash->{NAME};
  5921. my $sep_length = $hash->{hmccu}{queue}{sep_length};
  5922. my $element = '';
  5923. return undef if (!exists ($hash->{hmccu}{queue}));
  5924. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  5925. my $i;
  5926. while($hash->{hmccu}{queue}{queue}->sysread($_, $hash->{hmccu}{queue}{block_size})) {
  5927. $i = index($_, $hash->{hmccu}{queue}{seperator});
  5928. if($i != -1) {
  5929. $element .= substr($_, 0, $i);
  5930. $hash->{hmccu}{queue}{ptr} += $i + $sep_length;
  5931. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  5932. last;
  5933. }
  5934. else {
  5935. # Seperator not found, go back 'sep_length' spaces to ensure we don't miss it between reads
  5936. Log3 $name, 2, "HMCCU: HMCCU_QueueDeq seperator not found";
  5937. $element .= substr($_, 0, -$sep_length, '');
  5938. $hash->{hmccu}{queue}{ptr} += $hash->{hmccu}{queue}{block_size} - $sep_length;
  5939. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr}, SEEK_SET);
  5940. }
  5941. }
  5942. ## If queue seek pointer is at the EOF, truncate the queue file
  5943. if($hash->{hmccu}{queue}{queue}->sysread($_, 1) == 0)
  5944. {
  5945. $hash->{hmccu}{queue}{queue}->truncate(0) or return undef;
  5946. $hash->{hmccu}{queue}{queue}->sysseek($hash->{hmccu}{queue}{ptr} = 0, SEEK_SET);
  5947. }
  5948. ## Set idx file contents to point to the current seek position in queue file
  5949. $hash->{hmccu}{queue}{idx}->truncate(0) or return undef;
  5950. $hash->{hmccu}{queue}{idx}->sysseek(0, SEEK_SET);
  5951. $hash->{hmccu}{queue}{idx}->syswrite($hash->{hmccu}{queue}{ptr}) or return undef;
  5952. return ($element ne '') ? $element : undef;
  5953. }
  5954. ######################################################################
  5955. # *** HELPER FUNCTIONS ***
  5956. ######################################################################
  5957. ######################################################################
  5958. # Determine HomeMatic state considering datapoint values specified
  5959. # in attributes ccudef-hmstatevals and hmstatevals.
  5960. # Return (reading, channel, datapoint, value)
  5961. ######################################################################
  5962. sub HMCCU_GetHMState ($$$)
  5963. {
  5964. my ($name, $ioname, $defval) = @_;
  5965. my @hmstate = ('hmstate', undef, undef, $defval);
  5966. my $fnc = "GetHMState";
  5967. my $clhash = $defs{$name};
  5968. my $cltype = $clhash->{TYPE};
  5969. return @hmstate if ($cltype ne 'HMCCUDEV' && $cltype ne 'HMCCUCHN');
  5970. my $ghmstatevals = AttrVal ($ioname, 'ccudef-hmstatevals', $HMCCU_DEF_HMSTATE);
  5971. my $hmstatevals = AttrVal ($name, 'hmstatevals', $ghmstatevals);
  5972. $hmstatevals .= ";".$ghmstatevals if ($hmstatevals ne $ghmstatevals);
  5973. # Get reading name
  5974. if ($hmstatevals =~ /^=([^;]*);/) {
  5975. $hmstate[0] = $1;
  5976. $hmstatevals =~ s/^=[^;]*;//;
  5977. }
  5978. # Default hmstate is equal to state
  5979. $hmstate[3] = ReadingsVal ($name, 'state', undef) if (!defined ($defval));
  5980. # Substitute variables
  5981. $hmstatevals = HMCCU_SubstVariables ($clhash, $hmstatevals, undef);
  5982. my @rulelist = split (";", $hmstatevals);
  5983. foreach my $rule (@rulelist) {
  5984. my ($dptexpr, $subst) = split ('!', $rule, 2);
  5985. my $dp = '';
  5986. next if (!defined ($dptexpr) || !defined ($subst));
  5987. HMCCU_Trace ($clhash, 2, $fnc, "rule=$rule, dptexpr=$dptexpr, subst=$subst");
  5988. foreach my $d (keys %{$clhash->{hmccu}{dp}}) {
  5989. HMCCU_Trace ($clhash, 2, $fnc, "Check $d match $dptexpr");
  5990. if ($d =~ /$dptexpr/) {
  5991. $dp = $d;
  5992. last;
  5993. }
  5994. }
  5995. next if ($dp eq '');
  5996. my ($chn, $dpt) = split (/\./, $dp);
  5997. my $value = HMCCU_FormatReadingValue ($clhash, $clhash->{hmccu}{dp}{$dp}{VAL}, $hmstate[0]);
  5998. my ($rc, $newvalue) = HMCCU_SubstRule ($value, $subst, 0);
  5999. return ($hmstate[0], $chn, $dpt, $newvalue) if ($rc);
  6000. }
  6001. return @hmstate;
  6002. }
  6003. ######################################################################
  6004. # Calculate time difference in seconds between current time and
  6005. # specified timestamp
  6006. ######################################################################
  6007. sub HMCCU_GetTimeSpec ($)
  6008. {
  6009. my ($ts) = @_;
  6010. return -1 if ($ts !~ /^[0-9]{2}:[0-9]{2}$/ && $ts !~ /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/);
  6011. my (undef, $h, $m, $s) = GetTimeSpec ($ts);
  6012. return -1 if (!defined ($h));
  6013. $s += $h*3600+$m*60;
  6014. my @lt = localtime;
  6015. my $cs = $lt[2]*3600+$lt[1]*60+$lt[0];
  6016. $s += 86400 if ($cs > $s);
  6017. return ($s-$cs);
  6018. }
  6019. ######################################################################
  6020. # Calculate special readings. Requires hash of client device, channel
  6021. # number and datapoint. Supported functions:
  6022. # dewpoint, absolute humidity, increasing/decreasing counters,
  6023. # minimum/maximum, average, sum.
  6024. # Return readings array with reading/value pairs.
  6025. ######################################################################
  6026. sub HMCCU_CalculateReading ($$)
  6027. {
  6028. my ($cl_hash, $chkeys) = @_;
  6029. my $name = $cl_hash->{NAME};
  6030. my $fnc = "HMCCU_CalculateReading";
  6031. my @result = ();
  6032. my $ccucalculate = AttrVal ($name, 'ccucalculate', '');
  6033. return @result if ($ccucalculate eq '');
  6034. my @calclist = split (/[;\n]+/, $ccucalculate);
  6035. foreach my $calculation (@calclist) {
  6036. my ($vt, $rn, $dpts) = split (':', $calculation, 3);
  6037. next if (!defined ($dpts));
  6038. my $tmpdpts = ",$dpts,";
  6039. $tmpdpts =~ s/[\$\%\{\}]+//g;
  6040. HMCCU_Trace ($cl_hash, 2, $fnc, "vt=$vt, rn=$rn, dpts=$dpts, tmpdpts=$tmpdpts");
  6041. my $f = 0;
  6042. foreach my $chkey (@$chkeys) {
  6043. if ($tmpdpts =~ /,$chkey,/) {
  6044. $f = 1;
  6045. last;
  6046. }
  6047. }
  6048. next if ($f == 0);
  6049. my @dplist = split (',', $dpts);
  6050. # Get parameters values stored in device hash
  6051. my $newdpts = HMCCU_SubstVariables ($cl_hash, $dpts, undef);
  6052. my @pars = split (',', $newdpts);
  6053. my $pc = scalar (@pars);
  6054. next if ($pc != scalar(@dplist));
  6055. $f = 0;
  6056. for (my $i=0; $i<$pc; $i++) {
  6057. $pars[$i] =~ s/^#//;
  6058. if ($pars[$i] eq $dplist[$i]) {
  6059. $f = 1;
  6060. last;
  6061. }
  6062. }
  6063. next if ($f);
  6064. if ($vt eq 'dewpoint' || $vt eq 'abshumidity') {
  6065. # Dewpoint and absolute humidity
  6066. next if ($pc < 2);
  6067. my ($tmp, $hum) = @pars;
  6068. if ($tmp >= 0.0) {
  6069. $a = 7.5;
  6070. $b = 237.3;
  6071. }
  6072. else {
  6073. $a = 7.6;
  6074. $b = 240.7;
  6075. }
  6076. my $sdd = 6.1078*(10.0**(($a*$tmp)/($b+$tmp)));
  6077. my $dd = $hum/100.0*$sdd;
  6078. if ($dd != 0.0) {
  6079. if ($vt eq 'dewpoint') {
  6080. my $v = log($dd/6.1078)/log(10.0);
  6081. my $td = $b*$v/($a-$v);
  6082. push (@result, $rn, (sprintf "%.1f", $td));
  6083. }
  6084. else {
  6085. my $af = 100000.0*18.016/8314.3*$dd/($tmp+273.15);
  6086. push (@result, $rn, (sprintf "%.1f", $af));
  6087. }
  6088. }
  6089. }
  6090. elsif ($vt eq 'equ') {
  6091. # Set reading to value if all variables have the same value
  6092. next if ($pc < 1);
  6093. my $curval = shift @pars;
  6094. my $f = 1;
  6095. foreach my $newval (@pars) {
  6096. $f = 0 if ("$newval" ne "$curval");
  6097. }
  6098. push (@result, $rn, $f ? $curval : "n/a");
  6099. }
  6100. elsif ($vt eq 'min' || $vt eq 'max') {
  6101. # Minimum or maximum values
  6102. next if ($pc < 1);
  6103. my $curval = $pc > 1 ? shift @pars : ReadingsVal ($name, $rn, 0);
  6104. foreach my $newval (@pars) {
  6105. $curval = $newval if ($vt eq 'min' && $newval < $curval);
  6106. $curval = $newval if ($vt eq 'max' && $newval > $curval);
  6107. }
  6108. push (@result, $rn, $curval);
  6109. }
  6110. elsif ($vt eq 'inc' || $vt eq 'dec') {
  6111. # Increasing or decreasing values without reset
  6112. next if ($pc < 1);
  6113. my $newval = shift @pars;
  6114. my $oldval = ReadingsVal ($name, $rn."_old", 0);
  6115. my $curval = ReadingsVal ($name, $rn, 0);
  6116. if (($vt eq 'inc' && $newval < $curval) || ($vt eq 'dec' && $newval > $curval)) {
  6117. $oldval = $curval;
  6118. push (@result, $rn."_old", $oldval);
  6119. }
  6120. $curval = $newval+$oldval;
  6121. push (@result, $rn, $curval);
  6122. }
  6123. elsif ($vt eq 'avg') {
  6124. # Average value
  6125. next if ($pc < 1);
  6126. my $newval = shift @pars;
  6127. my $cnt = ReadingsVal ($name, $rn."_cnt", 0);
  6128. my $sum = ReadingsVal ($name, $rn."_sum", 0);
  6129. $cnt++;
  6130. $sum += $newval;
  6131. my $curval = $sum/$cnt;
  6132. push (@result, $rn."_cnt", $cnt, $rn."_sum", $sum, $rn, $curval);
  6133. }
  6134. elsif ($vt eq 'sum') {
  6135. # Sum of values
  6136. next if ($pc < 1);
  6137. my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0);
  6138. foreach my $newval (@pars) {
  6139. $curval += $newval;
  6140. }
  6141. push (@result, $rn, $curval);
  6142. }
  6143. elsif ($vt eq 'or') {
  6144. # Logical OR
  6145. next if ($pc < 1);
  6146. my $curval = $pc > 1 ? 0 : ReadingsVal ($name, $rn, 0);
  6147. foreach my $newval (@pars) {
  6148. $curval |= $newval;
  6149. }
  6150. push (@result, $rn, $curval);
  6151. }
  6152. elsif ($vt eq 'and') {
  6153. # Logical AND
  6154. next if ($pc < 1);
  6155. my $curval = $pc > 1 ? 1 : ReadingsVal ($name, $rn, 1);
  6156. foreach my $newval (@pars) {
  6157. $curval &= $newval;
  6158. }
  6159. push (@result, $rn, $curval);
  6160. }
  6161. }
  6162. return @result;
  6163. }
  6164. ######################################################################
  6165. # Encode command string for e-paper display
  6166. #
  6167. # Parameters:
  6168. #
  6169. # msg := parameter=value[,...]
  6170. #
  6171. # text1-3=Text
  6172. # icon1-3=IconName
  6173. # sound=SoundName
  6174. # signal=SignalName
  6175. # pause=1-160
  6176. # repeat=0-15
  6177. #
  6178. # Returns undef on error or encoded string on success
  6179. ######################################################################
  6180. sub HMCCU_EncodeEPDisplay ($)
  6181. {
  6182. my ($msg) = @_;
  6183. # set defaults
  6184. $msg = '' if (!defined ($msg));
  6185. my %disp_icons = (
  6186. ico_off => '0x80', ico_on => '0x81', ico_open => '0x82', ico_closed => '0x83',
  6187. ico_error => '0x84', ico_ok => '0x85', ico_info => '0x86', ico_newmsg => '0x87',
  6188. ico_svcmsg => '0x88'
  6189. );
  6190. my %disp_sounds = (
  6191. snd_off => '0xC0', snd_longlong => '0xC1', snd_longshort => '0xC2',
  6192. snd_long2short => '0xC3', snd_short => '0xC4', snd_shortshort => '0xC5',
  6193. snd_long => '0xC6'
  6194. );
  6195. my %disp_signals = (
  6196. sig_off => '0xF0', sig_red => '0xF1', sig_green => '0xF2', sig_orange => '0xF3'
  6197. );
  6198. # Parse command string
  6199. my @text = ('', '', '');
  6200. my @icon = ('', '', '');
  6201. my %conf = (sound => 'snd_off', signal => 'sig_off', repeat => 1, pause => 10);
  6202. foreach my $tok (split (',', $msg)) {
  6203. my ($par, $val) = split ('=', $tok);
  6204. next if (!defined ($val));
  6205. if ($par =~ /^text([1-3])$/) {
  6206. $text[$1-1] = substr ($val, 0, 12);
  6207. }
  6208. elsif ($par =~ /^icon([1-3])$/) {
  6209. $icon[$1-1] = $val;
  6210. }
  6211. elsif ($par =~ /^(sound|pause|repeat|signal)$/) {
  6212. $conf{$1} = $val;
  6213. }
  6214. }
  6215. my $cmd = '0x02,0x0A';
  6216. for (my $c=0; $c<3; $c++) {
  6217. if ($text[$c] ne '' || $icon[$c] ne '') {
  6218. $cmd .= ',0x12';
  6219. # Hex code
  6220. if ($text[$c] =~ /^0x[0-9A-F]{2}$/) {
  6221. $cmd .= ','.$text[$c];
  6222. }
  6223. # Predefined text code #0-9
  6224. elsif ($text[$c] =~ /^#([0-9])$/) {
  6225. $cmd .= sprintf (",0x8%1X", $1);
  6226. }
  6227. # Convert string to hex codes
  6228. else {
  6229. $text[$c] =~ s/\\_/ /g;
  6230. foreach my $ch (split ('', $text[$c])) {
  6231. $cmd .= sprintf (",0x%02X", ord ($ch));
  6232. }
  6233. }
  6234. # Icon
  6235. if ($icon[$c] ne '' && exists ($disp_icons{$icon[$c]})) {
  6236. $cmd .= ',0x13,'.$disp_icons{$icon[$c]};
  6237. }
  6238. }
  6239. $cmd .= ',0x0A';
  6240. }
  6241. # Sound
  6242. my $snd = $disp_sounds{snd_off};
  6243. $snd = $disp_sounds{$conf{sound}} if (exists ($disp_sounds{$conf{sound}}));
  6244. $cmd .= ',0x14,'.$snd.',0x1C';
  6245. # Repeat
  6246. my $rep = $conf{repeat} if ($conf{repeat} >= 0 && $conf{repeat} <= 15);
  6247. $rep = 1 if ($rep < 0);
  6248. $rep = 15 if ($rep > 15);
  6249. if ($rep == 0) {
  6250. $cmd .= ',0xDF';
  6251. }
  6252. else {
  6253. $cmd .= sprintf (",0x%02X", 0xD0+$rep-1);
  6254. }
  6255. $cmd .= ',0x1D';
  6256. # Pause
  6257. my $pause = $conf{pause};
  6258. $pause = 1 if ($pause < 1);
  6259. $pause = 160 if ($pause > 160);
  6260. $cmd .= sprintf (",0xE%1X,0x16", int(($pause-1)/10));
  6261. # Signal
  6262. my $sig = $disp_signals{sig_off};
  6263. $sig = $disp_signals{$conf{signal}} if (exists ($disp_signals{$conf{signal}}));
  6264. $cmd .= ','.$sig.',0x03';
  6265. return $cmd;
  6266. }
  6267. ######################################################################
  6268. # Convert reference to string recursively
  6269. # Supports reference to ARRAY, HASH and SCALAR and scalar values.
  6270. ######################################################################
  6271. sub HMCCU_RefToString ($)
  6272. {
  6273. my ($r) = @_;
  6274. my $result = '';
  6275. if (ref ($r) eq 'ARRAY') {
  6276. $result .= "[\n";
  6277. foreach my $e (@$r) {
  6278. $result .= "," if ($result ne '[');
  6279. $result .= HMCCU_RefToString ($e);
  6280. }
  6281. $result .= "\n]";
  6282. }
  6283. elsif (ref ($r) eq 'HASH') {
  6284. $result .= "{\n";
  6285. foreach my $k (sort keys %$r) {
  6286. $result .= "," if ($result ne '{');
  6287. $result .= "$k=".HMCCU_RefToString ($r->{$k});
  6288. }
  6289. $result .= "\n}";
  6290. }
  6291. elsif (ref ($r) eq 'SCALAR') {
  6292. $result .= $$r;
  6293. }
  6294. else {
  6295. $result .= $r;
  6296. }
  6297. return $result;
  6298. }
  6299. ######################################################################
  6300. # Match string with regular expression considering illegal regular
  6301. # expressions. Return parameter e if regular expression is incorrect.
  6302. ######################################################################
  6303. sub HMCCU_ExprMatch ($$$)
  6304. {
  6305. my ($t, $r, $e) = @_;
  6306. my $x = eval { $t =~ /$r/ };
  6307. return $e if (!defined ($x));
  6308. return "$x" eq '' ? 0 : 1;
  6309. }
  6310. sub HMCCU_ExprNotMatch ($$$)
  6311. {
  6312. my ($t, $r, $e) = @_;
  6313. my $x = eval { $t !~ /$r/ };
  6314. return $e if (!defined ($x));
  6315. return "$x" eq '' ? 0 : 1;
  6316. }
  6317. ######################################################################
  6318. # Read duty cycles of interfaces 2001 and 2010 and update readings.
  6319. ######################################################################
  6320. sub HMCCU_GetDutyCycle ($)
  6321. {
  6322. my ($hash) = @_;
  6323. my $name = $hash->{NAME};
  6324. my $dc = 0;
  6325. my @rpcports = HMCCU_GetRPCPortList ($hash);
  6326. readingsBeginUpdate ($hash);
  6327. foreach my $port (@rpcports) {
  6328. next if ($port != 2001 && $port != 2010);
  6329. my $url = HMCCU_GetRPCServerInfo ($hash, $port, 'url');
  6330. next if (!defined ($url));
  6331. my $rpcclient = RPC::XML::Client->new ($url);
  6332. my $response = $rpcclient->simple_request ("listBidcosInterfaces");
  6333. next if (!defined ($response) || ref($response) ne 'ARRAY');
  6334. foreach my $iface (@$response) {
  6335. next if (ref ($iface) ne 'HASH');
  6336. next if (!exists ($iface->{DUTY_CYCLE}));
  6337. $dc++;
  6338. my $type = exists ($iface->{TYPE}) ? $iface->{TYPE} : HMCCU_GetRPCServerInfo ($hash, $port, 'name');
  6339. readingsBulkUpdate ($hash, "iface_addr_$dc", $iface->{ADDRESS});
  6340. readingsBulkUpdate ($hash, "iface_conn_$dc", $iface->{CONNECTED});
  6341. readingsBulkUpdate ($hash, "iface_type_$dc", $type);
  6342. readingsBulkUpdate ($hash, "iface_ducy_$dc", $iface->{DUTY_CYCLE});
  6343. }
  6344. }
  6345. readingsEndUpdate ($hash, 1);
  6346. return $dc;
  6347. }
  6348. ######################################################################
  6349. # Check if TCP port is reachable.
  6350. # Parameter timeout should be a multiple of 20 plus 5.
  6351. ######################################################################
  6352. sub HMCCU_TCPPing ($$$)
  6353. {
  6354. my ($addr, $port, $timeout) = @_;
  6355. if ($timeout > 0) {
  6356. my $t = time ();
  6357. while (time () < $t+$timeout) {
  6358. return 1 if (HMCCU_TCPConnect ($addr, $port) ne '');
  6359. sleep (20);
  6360. }
  6361. return 0;
  6362. }
  6363. else {
  6364. return HMCCU_TCPConnect ($addr, $port) eq '' ? 0 : 1;
  6365. }
  6366. }
  6367. ######################################################################
  6368. # Check if TCP connection to specified host and port is possible.
  6369. # Return empty string on error or local IP address on success.
  6370. ######################################################################
  6371. sub HMCCU_TCPConnect ($$)
  6372. {
  6373. my ($addr, $port) = @_;
  6374. my $socket = IO::Socket::INET->new (PeerAddr => $addr, PeerPort => $port);
  6375. if ($socket) {
  6376. my $ipaddr = $socket->sockhost ();
  6377. close ($socket);
  6378. return $ipaddr if (defined ($ipaddr));
  6379. }
  6380. return '';
  6381. }
  6382. ######################################################################
  6383. # Resolve hostname.
  6384. # Return value defip if hostname can't be resolved.
  6385. ######################################################################
  6386. sub HMCCU_ResolveName ($$)
  6387. {
  6388. my ($hname, $defip) = @_;
  6389. my $ip = $defip;
  6390. my $addrnum = inet_aton ($hname);
  6391. $ip = inet_ntoa ($addrnum) if (defined ($addrnum));
  6392. return $ip;
  6393. }
  6394. ######################################################################
  6395. # Substitute invalid characters in reading name.
  6396. # Substitution rules: ':' => '.', any other illegal character => '_'
  6397. ######################################################################
  6398. sub HMCCU_CorrectName ($)
  6399. {
  6400. my ($rn) = @_;
  6401. $rn =~ s/\:/\./g;
  6402. $rn =~ s/[^A-Za-z\d_\.-]+/_/g;
  6403. return $rn;
  6404. }
  6405. ######################################################################
  6406. # *** SUBPROCESS PART ***
  6407. ######################################################################
  6408. # Child process. Must be global to allow access by RPC callbacks
  6409. my $hmccu_child;
  6410. # Queue file
  6411. my %child_queue;
  6412. my $cpqueue = \%child_queue;
  6413. # Statistic data of child process
  6414. my %child_hash = (
  6415. "total", 0,
  6416. "writeerror", 0,
  6417. "EV", 0,
  6418. "ND", 0,
  6419. "DD", 0,
  6420. "RD", 0,
  6421. "RA", 0,
  6422. "UD", 0,
  6423. "IN", 0,
  6424. "EX", 0,
  6425. "SL", 0
  6426. );
  6427. my $cphash = \%child_hash;
  6428. ######################################################################
  6429. # Subprocess: Write event to parent process
  6430. ######################################################################
  6431. sub HMCCU_CCURPC_Write ($$)
  6432. {
  6433. my ($et, $msg) = @_;
  6434. my $name = $hmccu_child->{devname};
  6435. $cphash->{total}++;
  6436. $cphash->{$et}++;
  6437. HMCCU_QueueEnq ($cpqueue, $et."|".$msg);
  6438. }
  6439. ######################################################################
  6440. # Subprocess: Initialize RPC server. Return 1 on success.
  6441. ######################################################################
  6442. sub HMCCU_CCURPC_OnRun ($)
  6443. {
  6444. $hmccu_child = shift;
  6445. my $name = $hmccu_child->{devname};
  6446. my $serveraddr = $hmccu_child->{serveraddr};
  6447. my $serverport = $hmccu_child->{serverport};
  6448. my $callbackport = $hmccu_child->{callbackport};
  6449. my $queuefile = $hmccu_child->{queue};
  6450. my $clkey = "CB".$serverport;
  6451. my $ccurpc_server;
  6452. # Create, open and reset queue file
  6453. Log3 $name, 0, "CCURPC: $clkey Creating file queue $queuefile";
  6454. if (!HMCCU_QueueOpen ($cpqueue, $queuefile)) {
  6455. Log3 $name, 0, "CCURPC: $clkey Can't create queue";
  6456. return 0;
  6457. }
  6458. # Reset event queue
  6459. HMCCU_QueueReset ($cpqueue);
  6460. while (defined (HMCCU_QueueDeq ($cpqueue))) { }
  6461. # Create RPC server
  6462. Log3 $name, 0, "CCURPC: Initializing RPC server $clkey";
  6463. $ccurpc_server = RPC::XML::Server->new (port=>$callbackport);
  6464. if (!ref($ccurpc_server))
  6465. {
  6466. Log3 $name, 0, "CCURPC: Can't create RPC callback server on port $callbackport. Port in use?";
  6467. return 0;
  6468. }
  6469. else {
  6470. Log3 $name, 0, "CCURPC: Callback server created listening on port $callbackport";
  6471. }
  6472. # Format of signature:
  6473. # string par1 ... parN
  6474. # Callback for events
  6475. # Parameters: Server, InterfaceId, Address, ValueKey, Value
  6476. Log3 $name, 1, "CCURPC: $clkey Adding callback for events";
  6477. $ccurpc_server->add_method (
  6478. { name=>"event",
  6479. signature=> ["string string string string string","string string string string int","string string string string double","string string string string boolean","string string string string i4"],
  6480. code=>\&HMCCU_CCURPC_EventCB
  6481. }
  6482. );
  6483. # Callback for new devices
  6484. # Parameters: Server, InterfaceId, DeviceDescriptions[]
  6485. Log3 $name, 1, "CCURPC: $clkey Adding callback for new devices";
  6486. $ccurpc_server->add_method (
  6487. { name=>"newDevices",
  6488. signature=>["string string array"],
  6489. code=>\&HMCCU_CCURPC_NewDevicesCB
  6490. }
  6491. );
  6492. # Callback for deleted devices
  6493. # Parameters: Server, InterfaceId, Addresses[]
  6494. Log3 $name, 1, "CCURPC: $clkey Adding callback for deleted devices";
  6495. $ccurpc_server->add_method (
  6496. { name=>"deleteDevices",
  6497. signature=>["string string array"],
  6498. code=>\&HMCCU_CCURPC_DeleteDevicesCB
  6499. }
  6500. );
  6501. # Callback for modified devices
  6502. # Parameters: Server, InterfaceId, Address, Hint
  6503. Log3 $name, 1, "CCURPC: $clkey Adding callback for modified devices";
  6504. $ccurpc_server->add_method (
  6505. { name=>"updateDevice",
  6506. signature=>["string string string int"],
  6507. code=>\&HMCCU_CCURPC_UpdateDeviceCB
  6508. }
  6509. );
  6510. # Callback for replaced devices
  6511. # Parameters: Server, InterfaceId, OldAddress, NewAddress
  6512. Log3 $name, 1, "CCURPC: $clkey Adding callback for replaced devices";
  6513. $ccurpc_server->add_method (
  6514. { name=>"replaceDevice",
  6515. signature=>["string string string string"],
  6516. code=>\&HMCCU_CCURPC_ReplaceDeviceCB
  6517. }
  6518. );
  6519. # Callback for readded devices
  6520. # Parameters: Server, InterfaceId, Addresses[]
  6521. Log3 $name, 1, "CCURPC: $clkey Adding callback for readded devices";
  6522. $ccurpc_server->add_method (
  6523. { name=>"replaceDevice",
  6524. signature=>["string string array"],
  6525. code=>\&HMCCU_CCURPC_ReaddDeviceCB
  6526. }
  6527. );
  6528. # Dummy implementation, always return an empty array
  6529. # Parameters: Server, InterfaceId
  6530. Log3 $name, 1, "CCURPC: $clkey Adding callback for list devices";
  6531. $ccurpc_server->add_method (
  6532. { name=>"listDevices",
  6533. signature=>["string string"],
  6534. code=>\&HMCCU_CCURPC_ListDevicesCB
  6535. }
  6536. );
  6537. # Enter server loop
  6538. HMCCU_CCURPC_Write ("SL", "$$|$clkey");
  6539. Log3 $name, 0, "CCURPC: $clkey Entering server loop";
  6540. $ccurpc_server->server_loop;
  6541. Log3 $name, 0, "CCURPC: $clkey Server loop terminated";
  6542. # Server loop exited by SIGINT
  6543. HMCCU_CCURPC_Write ("EX", "SHUTDOWN|$$|$clkey");
  6544. return 1;
  6545. }
  6546. ######################################################################
  6547. # Subprocess: Called when RPC server loop is terminated
  6548. ######################################################################
  6549. sub HMCCU_CCURPC_OnExit ()
  6550. {
  6551. # Output statistics
  6552. foreach my $et (sort keys %child_hash) {
  6553. Log3 $hmccu_child->{devname}, 2, "CCURPC: Eventcount $et = ".$cphash->{$et};
  6554. }
  6555. }
  6556. ######################################################################
  6557. # Subprocess: Callback for new devices
  6558. ######################################################################
  6559. sub HMCCU_CCURPC_NewDevicesCB ($$$)
  6560. {
  6561. my ($server, $cb, $a) = @_;
  6562. my $devcount = scalar (@$a);
  6563. my $name = $hmccu_child->{devname};
  6564. my $c = 0;
  6565. my $msg = '';
  6566. Log3 $name, 2, "CCURPC: $cb NewDevice received $devcount device specifications";
  6567. for my $dev (@$a) {
  6568. my $msg = '';
  6569. if ($dev->{ADDRESS} =~ /:[0-9]{1,2}$/) {
  6570. $msg = "C|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|null|null";
  6571. }
  6572. else {
  6573. # Wired devices do not have a RX_MODE attribute
  6574. my $rx = exists ($dev->{RX_MODE}) ? $dev->{RX_MODE} : 'null';
  6575. $msg = "D|".$dev->{ADDRESS}."|".$dev->{TYPE}."|".$dev->{VERSION}."|".
  6576. $dev->{FIRMWARE}."|".$rx;
  6577. }
  6578. HMCCU_CCURPC_Write ("ND", $msg);
  6579. }
  6580. return;
  6581. }
  6582. ######################################################################
  6583. # Subprocess: Callback for deleted devices
  6584. ######################################################################
  6585. sub HMCCU_CCURPC_DeleteDevicesCB ($$$)
  6586. {
  6587. my ($server, $cb, $a) = @_;
  6588. my $name = $hmccu_child->{devname};
  6589. my $devcount = scalar (@$a);
  6590. Log3 $name, 2, "CCURPC: $cb DeleteDevice received $devcount device addresses";
  6591. for my $dev (@$a) {
  6592. HMCCU_CCURPC_Write ("DD", $dev);
  6593. }
  6594. return;
  6595. }
  6596. ######################################################################
  6597. # Subprocess: Callback for modified devices
  6598. ######################################################################
  6599. sub HMCCU_CCURPC_UpdateDeviceCB ($$$$)
  6600. {
  6601. my ($server, $cb, $devid, $hint) = @_;
  6602. HMCCU_CCURPC_Write ("UD", $devid."|".$hint);
  6603. return;
  6604. }
  6605. ######################################################################
  6606. # Subprocess: Callback for replaced devices
  6607. ######################################################################
  6608. sub HMCCU_CCURPC_ReplaceDeviceCB ($$$$)
  6609. {
  6610. my ($server, $cb, $devid1, $devid2) = @_;
  6611. HMCCU_CCURPC_Write ("RD", $devid1."|".$devid2);
  6612. return;
  6613. }
  6614. ######################################################################
  6615. # Subprocess: Callback for readded devices
  6616. ######################################################################
  6617. sub HMCCU_CCURPC_ReaddDevicesCB ($$$)
  6618. {
  6619. my ($server, $cb, $a) = @_;
  6620. my $name = $hmccu_child->{devname};
  6621. my $devcount = scalar (@$a);
  6622. Log3 $name, 2, "CCURPC: $cb ReaddDevice received $devcount device addresses";
  6623. for my $dev (@$a) {
  6624. HMCCU_CCURPC_Write ("RA", $dev);
  6625. }
  6626. return;
  6627. }
  6628. ######################################################################
  6629. # Subprocess: Callback for handling CCU events
  6630. ######################################################################
  6631. sub HMCCU_CCURPC_EventCB ($$$$$)
  6632. {
  6633. my ($server, $cb, $devid, $attr, $val) = @_;
  6634. my $name = $hmccu_child->{devname};
  6635. HMCCU_CCURPC_Write ("EV", $devid."|".$attr."|".$val);
  6636. if (($cphash->{EV} % 500) == 0) {
  6637. Log3 $name, 3, "CCURPC: $cb Received 500 events from CCU since last check";
  6638. my @stkeys = ('total', 'EV', 'ND', 'DD', 'RD', 'RA', 'UD', 'IN', 'SL', 'EX');
  6639. my $msg = '';
  6640. foreach my $stkey (@stkeys) {
  6641. $msg .= '|' if ($msg ne '');
  6642. $msg .= $cphash->{$stkey};
  6643. }
  6644. HMCCU_CCURPC_Write ("ST", $msg);
  6645. }
  6646. # Never remove this statement!
  6647. return;
  6648. }
  6649. ######################################################################
  6650. # Subprocess: Callback for list devices
  6651. ######################################################################
  6652. sub HMCCU_CCURPC_ListDevicesCB ($$)
  6653. {
  6654. my ($server, $cb) = @_;
  6655. my $name = $hmccu_child->{devname};
  6656. $cb = "unknown" if (!defined ($cb));
  6657. Log3 $name, 1, "CCURPC: $cb ListDevices. Sending init to HMCCU";
  6658. HMCCU_CCURPC_Write ("IN", "INIT|1|$cb");
  6659. return RPC::XML::array->new();
  6660. }
  6661. 1;
  6662. =pod
  6663. =item device
  6664. =item summary provides interface between FHEM and Homematic CCU2
  6665. =begin html
  6666. <a name="HMCCU"></a>
  6667. <h3>HMCCU</h3>
  6668. <ul>
  6669. The module provides an interface between FHEM and a Homematic CCU2. HMCCU is the
  6670. I/O device for the client devices HMCCUDEV and HMCCUCHN. The module requires the
  6671. additional Perl modules IO::File, RPC::XML::Client, RPC::XML::Server and SubProcess
  6672. (part of FHEM).
  6673. </br></br>
  6674. <a name="HMCCUdefine"></a>
  6675. <b>Define</b><br/><br/>
  6676. <ul>
  6677. <code>define &lt;name&gt; HMCCU &lt;HostOrIP&gt; [&lt;ccu-number&gt;] [waitforccu=&lt;timeout&gt;]
  6678. [ccudelay=&lt;delay&gt;] [delayedinit=&lt;delay&gt;]</code>
  6679. <br/><br/>
  6680. Example:<br/>
  6681. <code>define myccu HMCCU 192.168.1.10 ccudelay=180</code>
  6682. <br/><br/>
  6683. The parameter <i>HostOrIP</i> is the hostname or IP address of a Homematic CCU2 or CCU3. If you have
  6684. more than one CCU you can specifiy a unique CCU number with parameter <i>ccu-number</i>. With
  6685. option <i>waitforccu</i> HMCCU will wait for the specified time if CCU is not reachable.
  6686. Parameter <i>timeout</i> should be a multiple of 20 in seconds. Warning: This option will
  6687. block the start of FHEM for <i>timeout</i> seconds.<br/>
  6688. The option <i>ccudelay</i> specifies the time for delayed initialization of CCU environment if
  6689. the CCU is not reachable during FHEM startup (i.e. in case of a power failure). The default value
  6690. for <i>delay</i> is 180 seconds. Increase this value if your CCU needs more time to start up
  6691. after a power failure. This option will not block the start of FHEM.<br/>
  6692. With option <i>delayedinit</i> the CCU ennvironment will be initialized after the specified time,
  6693. no matter if CCU is reachable or not. As long as CCU environment is not initialized all client
  6694. devices of type HMCCUCHN or HMCCUDEV are in state 'pending' and all commands are disabled.<br/><br/>
  6695. For automatic update of Homematic device datapoints and FHEM readings one have to:
  6696. <br/><br/>
  6697. <ul>
  6698. <li>Define used RPC interfaces with attribute 'rpcinterfaces'</li>
  6699. <li>Start RPC servers with command 'set rpcserver on'</li>
  6700. <li>Optionally enable automatic start of RPC servers with attribute 'rpcserver'</li>
  6701. </ul><br/>
  6702. Then start with the definition of client devices using modules HMCCUDEV (CCU devices)
  6703. and HMCCUCHN (CCU channels) or with command 'get devicelist create'.<br/>
  6704. Maybe it's helpful to set the following FHEM standard attributes for the HMCCU I/O
  6705. device:<br/><br/>
  6706. <ul>
  6707. <li>Shortcut for RPC server control: eventMap /rpcserver on:on/rpcserver off:off/</li>
  6708. </ul>
  6709. </ul>
  6710. <br/>
  6711. <a name="HMCCUset"></a>
  6712. <b>Set</b><br/><br/>
  6713. <ul>
  6714. <li><b>set &lt;name&gt; ackmessages</b><br/>
  6715. Acknowledge device was unreachable messages in CCU.
  6716. </li><br/>
  6717. <li><b>set &lt;name&gt; cleardefaults</b><br/>
  6718. Clear default attributes imported from file.
  6719. </li><br/>
  6720. <li><b>set &lt;name&gt; defaults</b><br/>
  6721. Set default attributes for I/O device.
  6722. </li><br/>
  6723. <li><b>set &lt;name&gt; delete &lt;ccuobject&gt; [&lt;objecttype&gt;]</b><br/>
  6724. Delete object in CCU. Default object type is OT_VARDP. Valid object types are<br/>
  6725. OT_DEVICE=device, OT_VARDP=variable.
  6726. </li><br/>
  6727. <li><b>set &lt;name&gt; execute &lt;program&gt;</b><br/>
  6728. Execute a CCU program.
  6729. <br/><br/>
  6730. Example:<br/>
  6731. <code>set d_ccu execute PR-TEST</code>
  6732. </li><br/>
  6733. <li><b>set &lt;name&gt; hmscript {&lt;script-file&gt;|'!'&lt;function&gt;|'['&lt;code&gt;']'} [dump]
  6734. [&lt;parname&gt;=&lt;value&gt; [...]]</b><br/>
  6735. Execute Homematic script on CCU. If script code contains parameter in format $parname
  6736. they are substituted by corresponding command line parameters <i>parname</i>.<br/>
  6737. If output of script contains lines in format Object=Value readings in existing
  6738. corresponding FHEM devices will be set. <i>Object</i> can be the name of a CCU system
  6739. variable or a valid channel and datapoint specification. Readings for system variables
  6740. are set in the I/O device. Datapoint related readings are set in client devices. If option
  6741. 'dump' is specified the result of script execution is displayed in FHEM web interface.
  6742. Execute command without parameters will list available script functions.
  6743. </li><br/>
  6744. <li><b>set &lt;name&gt; importdefaults &lt;filename&gt;</b><br/>
  6745. Import default attributes from file.
  6746. </li><br/>
  6747. <li><b>set &lt;name&gt; rpcregister [{all | &lt;interface&gt;}]</b><br/>
  6748. Register RPC servers at CCU.
  6749. </li><br/>
  6750. <li><b>set &lt;name&gt; rpcserver {on | off | restart}</b><br/>
  6751. Start, stop or restart RPC server(s). This command executed with option 'on'
  6752. will fork a RPC server process for each RPC interface defined in attribute 'rpcinterfaces'.
  6753. Until operation is completed only a few set/get commands are available and you
  6754. may get the error message 'CCU busy'.
  6755. </li><br/>
  6756. <li><b>set &lt;name&gt; var &lt;variable&gt; &lt;Value&gt;</b><br/>
  6757. Set CCU system variable value. Special characters \_ in <i>value</i> are
  6758. substituted by blanks.
  6759. </li>
  6760. </ul>
  6761. <br/>
  6762. <a name="HMCCUget"></a>
  6763. <b>Get</b><br/><br/>
  6764. <ul>
  6765. <li><b>get &lt;name&gt; aggregation {&lt;rule&gt;|all}</b><br/>
  6766. Process aggregation rule defined with attribute ccuaggregate.
  6767. </li><br/>
  6768. <li><b>get &lt;name&gt; configdesc {&lt;device&gt;|&lt;channel&gt;}</b><br/>
  6769. Get configuration parameter description of CCU device or channel (similar
  6770. to device settings in CCU). Not every CCU device or channel provides a configuration
  6771. parameter description. So result may be empty.
  6772. </li><br/>
  6773. <li><b>get &lt;name&gt; defaults</b><br/>
  6774. List device types and channels with default attributes available.
  6775. </li><br/>
  6776. <li><b>get &lt;name&gt; deviceinfo &lt;device-name&gt; [{State | <u>Value</u>}]</b><br/>
  6777. List device channels and datapoints. If option 'State' is specified the device is
  6778. queried directly. Otherwise device information from CCU is listed.
  6779. </li><br/>
  6780. <li><b>get &lt;name&gt; devicelist [dump]</b><br/>
  6781. Read list of devices and channels from CCU. This command is executed automatically
  6782. after the definition of an I/O device. It must be executed manually after
  6783. module HMCCU is reloaded or after devices have changed in CCU (added, removed or
  6784. renamed). With option 'dump' devices are displayed in browser window. If a RPC
  6785. server is running HMCCU will raise events "<i>count</i> devices added in CCU" or
  6786. "<i>count</i> devices deleted in CCU". It's recommended to set up a notification
  6787. which reacts with execution of command 'get devicelist' on these events.
  6788. </li><br/>
  6789. <li><b>get &lt;name&gt; devicelist create &lt;devexp&gt; [t={chn|<u>dev</u>|all}]
  6790. [p=&lt;prefix&gt;] [s=&lt;suffix&gt;] [f=&lt;format&gt;] [defattr] [duplicates]
  6791. [save] [&lt;attr&gt;=&lt;value&gt; [...]]</b><br/>
  6792. With option 'create' HMCCU will automatically create client devices for all CCU devices
  6793. and channels matching specified regular expression. With option t=chn or t=dev (default)
  6794. the creation of devices is limited to CCU channels or devices.<br/>
  6795. Optionally a <i>prefix</i> and/or a
  6796. <i>suffix</i> for the FHEM device name can be specified. The parameter <i>format</i>
  6797. defines a template for the FHEM device names. Prefix, suffix and format can contain
  6798. format identifiers which are substituted by corresponding values of the CCU device or
  6799. channel: %n = CCU object name (channel or device), %d = CCU device name, %a = CCU address.
  6800. In addition a list of default attributes for the created client devices can be specified.
  6801. If option 'defattr' is specified HMCCU tries to set default attributes for device.
  6802. With option 'duplicates' HMCCU will overwrite existing devices and/or create devices
  6803. for existing device addresses. Option 'save' will save FHEM config after device definition.
  6804. </li><br/>
  6805. <li><b>get &lt;name&gt; dump {datapoints|devtypes} [&lt;filter&gt;]</b><br/>
  6806. Dump all Homematic devicetypes or all devices including datapoints currently
  6807. defined in FHEM.
  6808. </li><br/>
  6809. <li><b>get &lt;name&gt; dutycycle</b><br/>
  6810. Read CCU interface and gateway information. For each interface/gateway the following
  6811. information is stored in readings:<br/>
  6812. iface_addr_n = interface address<br/>
  6813. iface_type_n = interface type<br/>
  6814. iface_conn_n = interface connection state (1=connected, 0=disconnected)<br/>
  6815. iface_ducy_n = duty cycle of interface (0-100)
  6816. </li><br/>
  6817. <li><b>get &lt;name&gt; exportdefaults &lt;filename&gt; [csv] [all]</b><br/>
  6818. Export default attributes into file. If option <i>all</i> is specified, also defaults imported
  6819. by customer will be exported.
  6820. </li><br/>
  6821. <li><b>get &lt;name&gt; firmware [{&lt;type-expr&gt; | full}]</b><br/>
  6822. Get available firmware downloads from eq-3.de. List FHEM devices with current and available
  6823. firmware version. By default only firmware version of defined HMCCUDEV or HMCCUCHN
  6824. devices are listet. With option 'full' all available firmware versions are listed.
  6825. With parameter <i>type-expr</i> one can filter displayed firmware versions by
  6826. Homematic device type.
  6827. </li><br/>
  6828. <li><b>get &lt;name&gt; parfile [&lt;parfile&gt;]</b><br/>
  6829. Get values of all channels / datapoints specified in <i>parfile</i>. The parameter
  6830. <i>parfile</i> can also be defined as an attribute. The file must contain one channel /
  6831. definition per line.
  6832. <br/><br/>
  6833. The syntax of Parfile entries is:
  6834. <br/><br/>
  6835. {[&lt;interface&gt;.]&lt;channel-address&gt; | &lt;channel-name&gt;}
  6836. [.&lt;datapoint-expr&gt;] [&lt;subst-rules&gt;]
  6837. <br/><br/>
  6838. Empty lines or lines starting with a # are ignored.
  6839. </li><br/>
  6840. <li><b>get &lt;name&gt; rpcstate</b><br/>
  6841. Check if RPC server process is running.
  6842. </li><br/>
  6843. <li><b>get &lt;name&gt; update [&lt;devexp&gt; [{State | <u>Value</u>}]]</b><br/>
  6844. Update all datapoints / readings of client devices with <u>FHEM device name</u>(!) matching
  6845. <i>devexp</i>. With option 'State' all CCU devices are queried directly. This can be
  6846. time consuming.
  6847. </li><br/>
  6848. <li><b>get &lt;name&gt; updateccu [&lt;devexp&gt; [{State | <u>Value</u>}]]</b><br/>
  6849. Update all datapoints / readings of client devices with <u>CCU device name</u>(!) matching
  6850. <i>devexp</i>. With option 'State' all CCU devices are queried directly. This can be
  6851. time consuming.
  6852. </li><br/>
  6853. <li><b>get &lt;name&gt; vars &lt;regexp&gt;</b><br/>
  6854. Get CCU system variables matching <i>regexp</i> and store them as readings.
  6855. </li>
  6856. </ul>
  6857. <br/>
  6858. <a name="HMCCUattr"></a>
  6859. <b>Attributes</b><br/>
  6860. <br/>
  6861. <ul>
  6862. <li><b>ccuaggregate &lt;rule&gt;[;...]</b><br/>
  6863. Define aggregation rules for client device readings. With an aggregation rule
  6864. it's easy to detect if some or all client device readings are set to a specific
  6865. value, i.e. detect all devices with low battery or detect all open windows.<br/>
  6866. Aggregation rules are automatically executed as a reaction on reading events of
  6867. HMCCU client devices. An aggregation rule consists of several parameters separated
  6868. by comma:<br/><br/>
  6869. <ul>
  6870. <li><b>name:&lt;rule-name&gt;</b><br/>
  6871. Name of aggregation rule</li>
  6872. <li><b>filter:{name|alias|group|room|type}=&lt;incl-expr&gt;[!&lt;excl-expr&gt;]</b><br/>
  6873. Filter criteria, i.e. "type=^HM-Sec-SC.*"</li>
  6874. <li><b>read:&lt;read-expr&gt;</b><br/>
  6875. Expression for reading names, i.e. "STATE"</li>
  6876. <li><b>if:{any|all|min|max|sum|avg|lt|gt|le|ge}=&lt;value&gt;</b><br/>
  6877. Condition, i.e. "any=open" or initial value, i.e. max=0</li>
  6878. <li><b>else:&lt;value&gt;</b><br/>
  6879. Complementary value, i.e. "closed"</li>
  6880. <li><b>prefix:{&lt;text&gt;|RULE}</b><br/>
  6881. Prefix for reading names with aggregation results</li>
  6882. <li><b>coll:{&lt;attribute&gt;|NAME}[!&lt;default-text&gt;]</b><br/>
  6883. Attribute of matching devices stored in aggregation results. Default text in case
  6884. of no matching devices found is optional.</li>
  6885. <li><b>html:&lt;template-file&gt;</b><br/>
  6886. Create HTML code with matching devices.</li>
  6887. </ul><br/>
  6888. Aggregation results will be stored in readings <i>prefix</i>count, <i>prefix</i>list,
  6889. <i>prefix</i>match, <i>prefix</i>state and <i>prefix</i>table.<br/><br/>
  6890. Format of a line in <i>template-file</i> is &lt;keyword&gt;:&lt;html-code&gt;. See
  6891. FHEM Wiki for an example. Valid keywords are:<br/><br/>
  6892. <ul>
  6893. <li><b>begin-html</b>: Start of html code.</li>
  6894. <li><b>begin-table</b>: Start of table (i.e. the table header)</li>
  6895. <li><b>row-odd</b>: HTML code for odd lines. A tag &lt;reading/&gt is replaced by a matching device.</li>
  6896. <li><b>row-even</b>: HTML code for event lines.</li>
  6897. <li><b>end-table</b>: End of table.</li>
  6898. <li><b>default</b>: HTML code for no matches.</li>
  6899. <li><b>end-html</b>: End of html code.</li>
  6900. </ul><br/>
  6901. Example: Find open windows<br/>
  6902. name=lock,filter:type=^HM-Sec-SC.*,read:STATE,if:any=open,else:closed,prefix:lock_,coll:NAME!All windows closed<br/><br/>
  6903. Example: Find devices with low batteries. Generate reading in HTML format.<br/>
  6904. 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/>
  6905. </li><br/>
  6906. <li><b>ccudef-hmstatevals &lt;subst-rule[;...]&gt;</b><br/>
  6907. Set global rules for calculation of reading hmstate.
  6908. </li><br/>
  6909. <li><b>ccudef-readingfilter &lt;filter-rule[;...]&gt;</b><br/>
  6910. Set global reading/datapoint filter. This filter is added to the filter specified by
  6911. client device attribute 'ccureadingfilter'.
  6912. </li><br/>
  6913. <li><b>ccudef-readingformat {name | address | <u>datapoint</u> | namelc | addresslc |
  6914. datapointlc}</b><br/>
  6915. Set global reading format. This format is the default for all readings except readings
  6916. of virtual device groups.
  6917. </li><br/>
  6918. <li><b>ccudef-readingname &lt;old-readingname-expr&gt;:[+]&lt;new-readingname&gt;
  6919. [;...]</b><br/>
  6920. Set global rules for reading name substitution. These rules are added to the rules
  6921. specified by client device attribute 'ccureadingname'.
  6922. </li><br/>
  6923. <li><b>ccudef-substitute &lt;subst-rule&gt;[;...]</b><br/>
  6924. Set global substitution rules for datapoint value. These rules are added to the rules
  6925. specified by client device attribute 'substitute'.
  6926. </li><br/>
  6927. <li><b>ccudefaults &lt;filename&gt;</b><br/>
  6928. Load default attributes for HMCCUCHN and HMCCUDEV devices from specified file. Best
  6929. practice for creating a custom default attribute file is by exporting predefined default
  6930. attributes from HMCCU with command 'get exportdefaults'.
  6931. </li><br/>
  6932. <li><b>ccuflags {&lt;flags&gt;}</b><br/>
  6933. Control behaviour of several HMCCU functions. Parameter <i>flags</i> is a comma
  6934. seperated list of the following strings:<br/>
  6935. ackState - Acknowledge command execution by setting STATE to error or success.<br/>
  6936. dptnocheck - Do not check within set or get commands if datapoint is valid<br/>
  6937. intrpc - Use internal RPC server. This is the default.<br/>
  6938. extrpc - Same as procrpc (see below)<br/>
  6939. logEvents - Write events from CCU into FHEM logfile<br/>
  6940. noEvents - Ignore events / device updates sent by CCU. No readings will be updated!<br/>
  6941. nonBlocking - Use non blocking (asynchronous) CCU requests<br/>
  6942. noReadings - Do not create or update readings<br/>
  6943. procrpc - Use external RPC server provided by module HMCCPRPCPROC. During first RPC
  6944. server start HMCCU will create a HMCCURPCPROC device for each interface confiugured
  6945. in attribute 'rpcinterface'<br/>
  6946. Flags intrpc, extrpc and procrpc cannot be combined.
  6947. </li><br/>
  6948. <li><b>ccuget {State | <u>Value</u>}</b><br/>
  6949. Set read access method for CCU channel datapoints. Method 'State' is slower than
  6950. 'Value' because each request is sent to the device. With method 'Value' only CCU
  6951. is queried. Default is 'Value'. Method for write access to datapoints is always
  6952. 'State'.
  6953. </li><br/>
  6954. <li><b>ccuGetVars &lt;interval&gt;[&lt;pattern&gt;]</b><br/>
  6955. Read CCU system variables periodically and update readings. If pattern is specified
  6956. only variables matching this expression are stored as readings.
  6957. </li><br/>
  6958. <li><b>ccuReqTimeout &lt;Seconds&gt;</b><br/>
  6959. Set timeout for CCU request. Default is 4 seconds. This timeout affects several
  6960. set and get commands, i.e. "set datapoint" or "set var". If a command runs into
  6961. a timeout FHEM will block for <i>Seconds</i>. To prevent blocking set flag 'nonBlocking'
  6962. in attribute <i>ccuflags</i>.
  6963. </li><br/>
  6964. <li><b>ccureadings {0 | <u>1</u>}</b><br/>
  6965. Deprecated. Readings are written by default. To deactivate readings set flag noReadings
  6966. in attribute ccuflags.
  6967. </li><br/>
  6968. <li><b>parfile &lt;filename&gt;</b><br/>
  6969. Define parameter file for command 'get parfile'.
  6970. </li><br/>
  6971. <li><b>rpcinterfaces &lt;interface&gt;[,...]</b><br/>
  6972. Specify list of CCU RPC interfaces. HMCCU will register a RPC server for each interface.
  6973. Interface BidCos-RF is default and always active. Valid interfaces are:<br/><br/>
  6974. <ul>
  6975. <li>BidCos-Wired (Port 2000)</li>
  6976. <li>BidCos-RF (Port 2001)</li>
  6977. <li>Homegear (Port 2003)</li>
  6978. <li>HmIP-RF (Port 2010)</li>
  6979. <li>HVL (Port 7000)</li>
  6980. <li>CUxD (Port 8701)</li>
  6981. <li>VirtualDevice (Port 9292)</li>
  6982. </ul>
  6983. </li><br/>
  6984. <li><b>rpcinterval &lt;Seconds&gt;</b><br/>
  6985. Specifiy how often RPC queue is read. Default is 5 seconds. Only relevant if internal
  6986. RPC server is used (deprecated).
  6987. </li><br/>
  6988. <li><b>rpcport &lt;value[,...]&gt;</b><br/>
  6989. Deprecated, use attribute 'rpcinterfaces' instead. Specify list of RPC ports on CCU.
  6990. Default is 2001. Valid RPC ports are:<br/><br/>
  6991. <ul>
  6992. <li>2000 = Wired components</li>
  6993. <li>2001 = BidCos-RF (wireless 868 MHz components with BidCos protocol)</li>
  6994. <li>2003 = Homegear (experimental)</li>
  6995. <li>2010 = HM-IP (wireless 868 MHz components with IPv6 protocol)</li>
  6996. <li>7000 = HVL (Homematic Virtual Layer devices)</li>
  6997. <li>8701 = CUxD (only supported with external RPC server HMCCURPC)</li>
  6998. <li>9292 = CCU group devices (especially heating groups)</li>
  6999. </ul>
  7000. </li><br/>
  7001. <li><b>rpcqueue &lt;queue-file&gt;</b><br/>
  7002. Specify name of RPC queue file. This parameter is only a prefix (including the
  7003. pathname) for the queue files with extension .idx and .dat. Default is
  7004. /tmp/ccuqueue. If FHEM is running on a SD card it's recommended that the queue
  7005. files are placed on a RAM disk.
  7006. </li><br/>
  7007. <li><b>rpcserver {on | <u>off</u>}</b><br/>
  7008. Specify if RPC server is automatically started on FHEM startup.
  7009. </li><br/>
  7010. <li><b>rpcserveraddr &lt;ip-or-name&gt;</b><br/>
  7011. Specify network interface by IP address or DNS name where RPC server should listen
  7012. on. By default HMCCU automatically detects the IP address. This attribute should be used
  7013. if the FHEM server has more than one network interface.
  7014. </li><br/>
  7015. <li><b>rpcserverport &lt;base-port&gt;</b><br/>
  7016. Specify base port for RPC server. The real listening port of an RPC server is
  7017. calculated by the formula: base-port + rpc-port + (10 * ccu-number). Default
  7018. value for <i>base-port</i> is 5400.<br/>
  7019. The value ccu-number is only relevant if more than one CCU is connected to FHEM.
  7020. Example: If <i>base-port</i> is 5000, protocol is BidCos (rpc-port 2001) and only
  7021. one CCU is connected the resulting RPC server port is 5000+2001+(10*0) = 7001.
  7022. </li><br/>
  7023. <li><b>substitute &lt;subst-rule&gt;:&lt;substext&gt;[,...]</b><br/>
  7024. Define substitions for datapoint values. Syntax of <i>subst-rule</i> is<br/><br/>
  7025. [[&lt;channelno.&gt;]&lt;datapoint&gt;[,...]!]&lt;{#n1-m1|regexp1}&gt;:&lt;text1&gt;[,...]
  7026. <br/>
  7027. Substitutions for parfile values must be specified in parfiles.
  7028. </li><br/>
  7029. <li><b>stripchar &lt;character&gt;</b><br/>
  7030. Strip the specified character from variable or device name in set commands. This
  7031. is useful if a variable should be set in CCU using the reading with trailing colon.
  7032. </li>
  7033. </ul>
  7034. </ul>
  7035. =end html
  7036. =cut