88_HMCCU.pm 176 KB

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