93_DbRep.pm 395 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464
  1. ##########################################################################################################
  2. # $Id: 93_DbRep.pm 15446 2017-11-18 19:18:56Z DS_Starter $
  3. ##########################################################################################################
  4. # 93_DbRep.pm
  5. #
  6. # (c) 2016-2017 by Heiko Maaz
  7. # e-mail: Heiko dot Maaz at t-online dot de
  8. #
  9. # This Module can be used to select and report content of databases written by 93_DbLog module
  10. # in different manner.
  11. #
  12. # This script is part of fhem.
  13. #
  14. # Fhem is free software: you can redistribute it and/or modify
  15. # it under the terms of the GNU General Public License as published by
  16. # the Free Software Foundation, either version 2 of the License, or
  17. # (at your option) any later version.
  18. #
  19. # Fhem is distributed in the hope that it will be useful,
  20. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. # GNU General Public License for more details.
  23. #
  24. # You should have received a copy of the GNU General Public License
  25. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  26. #
  27. # Credits:
  28. # - viegener for some input
  29. # - some proposals to boost and improve SQL-Statements by JoeALLb
  30. #
  31. ###########################################################################################################################
  32. #
  33. # Definition: define <name> DbRep <DbLog-Device>
  34. #
  35. # This module uses credentials of the DbLog-Device
  36. #
  37. ###########################################################################################################################
  38. # Versions History:
  39. #
  40. # 6.0.0 18.11.2017 FTP transfer dumpfile after dump, delete old dumpfiles within Blockingcall (avoid freezes)
  41. # commandref revised, minor fixes
  42. # 5.8.6 30.10.2017 don't limit attr reading, device if the attr contains a list
  43. # 5.8.5 19.10.2017 filter unwanted characters in "procinfo"-result
  44. # 5.8.4 17.10.2017 createSelectSql, createDeleteSql, currentfillup_Push switch to devspec
  45. # 5.8.3 16.10.2017 change to use createSelectSql: minValue,diffValue - createDeleteSql: delEntries
  46. # 5.8.2 15.10.2017 sub createTimeArray
  47. # 5.8.1 15.10.2017 change to use createSelectSql: sumValue,averageValue,exportToFile,maxValue
  48. # 5.8.0 15.10.2017 adapt createSelectSql for better performance if time/aggregation not set,
  49. # can set table as flexible argument for countEntries, fetchrows (default: history),
  50. # minor fixes
  51. # 5.7.1 13.10.2017 tableCurrentFillup fix for PostgreSQL, commandref revised
  52. # 5.7.0 09.10.2017 tableCurrentPurge, tableCurrentFillup
  53. # 5.6.4 05.10.2017 abortFn's adapted to use abortArg (Forum:77472)
  54. # 5.6.3 01.10.2017 fix crash of fhem due to wrong rmday-calculation if month is changed, Forum:#77328
  55. # 5.6.2 28.08.2017 commandref revised
  56. # 5.6.1 18.07.2017 commandref revised, minor fixes
  57. # 5.6.0 17.07.2017 default timeout changed to 86400, new get-command "procinfo" (MySQL)
  58. # 5.5.2 16.07.2017 dbmeta_DoParse -> show variables (no global)
  59. # 5.5.1 16.07.2017 wrong text output in state when restoreMySQL was aborted by timeout
  60. # 5.5.0 10.07.2017 replace $hash->{dbloghash}{DBMODEL} by $hash->{dbloghash}{MODEL} (DbLog was changed)
  61. # 5.4.0 03.07.2017 restoreMySQL - restore of csv-files (from dumpServerSide),
  62. # RestoreRowsHistory/ DumpRowsHistory, Commandref revised
  63. # 5.3.1 28.06.2017 vacuum for SQLite added, readings enhanced for optimizeTables / vacuum, commandref revised
  64. # 5.3.0 26.06.2017 change of mysql_optimize_tables, new command optimizeTables
  65. # 5.2.1 25.06.2017 bugfix in sqlCmd_DoParse (PRAGMA, UTF8, SHOW)
  66. # 5.2.0 14.06.2017 UTF-8 support for MySQL (fetchrows, srvinfo, expfile, impfile, insert)
  67. # 5.1.0 13.06.2017 column "UNIT" added to fetchrow result
  68. # 5.0.6 13.06.2017 add Aria engine to mysql_optimize_tables
  69. # 5.0.5 12.06.2017 bugfixes in DumpAborted, some changes in dumpMySQL, optimizeTablesBeforeDump added to
  70. # mysql_DoDumpServerSide, new reading DumpFileCreatedSize
  71. # 5.0.4 09.06.2017 some improvements and changes of mysql_DoDump, commandref revised, new attributes
  72. # executeBeforeDump, executeAfterDump
  73. # 5.0.3 07.06.2017 mysql_DoDumpServerSide added
  74. # 5.0.2 06.06.2017 little improvements in mysql_DoDumpClientSide
  75. # 5.0.1 05.06.2017 dependencies between dumpMemlimit and dumpSpeed created, enhanced verbose 5 logging
  76. # 5.0.0 04.06.2017 MySQL Dump nonblocking added
  77. # 4.16.1 22.05.2017 encode json without JSON module, requires at least fhem.pl 14348 2017-05-22 20:25:06Z
  78. # 4.16.0 22.05.2017 format json as option of sqlResultFormat, state will never be deleted in "delread"
  79. # 4.15.1 20.05.2017 correction of commandref
  80. # 4.15.0 17.05.2017 SUM(VALUE),AVG(VALUE) recreated for PostgreSQL, Code reviewed and optimized
  81. # 4.14.2 16.05.2017 SQL-Statements optimized for Wildcard "%" usage if used, Wildcard "_" isn't supported
  82. # furthermore, "averageValue", "sumValue", "maxValue", "minValue", "countEntries"
  83. # performance optimized,
  84. # commandref revised
  85. # 4.14.1 16.05.2017 limitation of fetchrows result datasets to 1000 by attr limit
  86. # 4.14.0 15.05.2017 UserExitFn added as separate sub (userexit) and attr userExitFn defined,
  87. # new subs ReadingsBulkUpdateTimeState, ReadingsBulkUpdateValue,
  88. # ReadingsSingleUpdateValue, commandref revised
  89. # 4.13.7 11.05.2017 attribute sqlResultSingleFormat became sqlResultFormat, sqlResultSingle deleted and
  90. # sqlCmd contains now all format possibilities (separated,mline,sline,table),
  91. # commandref revised
  92. # 4.13.6 10.05.2017 minor changes
  93. # 4.13.5 09.05.2017 cover dbh prepare in eval to avoid crash (sqlResult_DoParse)
  94. # 4.13.4 09.05.2017 attribute sqlResultSingleFormat: mline sline table, attribute "allowDeletion" is now
  95. # also valid for sqlResult, sqlResultSingle and delete command is forced
  96. # 4.13.3 09.05.2017 flexible format of reading SqlResultRow_xxx for proper and sort sequence
  97. # 4.13.2 09.05.2017 sqlResult, sqlResultSingle are able to execute delete, insert, update commands
  98. # error corrections
  99. # 4.13.1 09.05.2017 change substitution in sqlResult, sqlResult_DoParse
  100. # 4.13.0 09.05.2017 acceptance of viegener change with some corrections (separating lines with ]|[ in Singleline)
  101. # viegener 07.05.2017 New sets sqlSelect execute arbitrary sql command returning each row as single reading (fields separated with |)
  102. # allowing replacement of timestamp values according to attribute definition --> §timestamp_begin§ etc
  103. # and sqlSelectSingle for executing an sql command returning a single reading (separating lines with §)
  104. # 4.12.2 17.04.2017 DbRep_checkUsePK changed
  105. # 4.12.1 07.04.2017 get tableinfo changed for MySQL
  106. # 4.12.0 31.03.2017 support of primary key for insert functions
  107. # 4.11.4 29.03.2017 bugfix timestamp in minValue, maxValue if VALUE contains more than one
  108. # numeric value (like in sysmon)
  109. # 4.11.3 26.03.2017 usage of daylight saving time changed to avoid wrong selection when wintertime
  110. # switch to summertime, minor bug fixes
  111. # 4.11.2 16.03.2017 bugfix in func dbmeta_DoParse (SQLITE_DB_FILENAME)
  112. # 4.11.1 28.02.2017 commandref completed
  113. # 4.11.0 18.02.2017 added [current|previous]_[month|week|day|hour]_begin and
  114. # [current|previous]_[month|week|day|hour]_end as options of timestamp
  115. # 4.10.3 01.02.2017 rename reading "diff-overrun_limit-" to "diff_overrun_limit_",
  116. # collaggstr day aggregation changed back from 4.7.5 change
  117. # 4.10.2 16.01.2017 bugfix uninitialized value $renmode if RenameAgent
  118. # 4.10.1 30.11.2016 bugfix importFromFile format problem if UNIT-field wasn't set
  119. # 4.10 28.12.2016 del_DoParse changed to use Wildcards, del_ParseDone changed to use readingNameMap
  120. # 4.9 23.12.2016 function readingRename added
  121. # 4.8.6 17.12.2016 new bugfix group by-clause due to incompatible changes made in MyQL 5.7.5
  122. # (Forum #msg541103)
  123. # 4.8.5 16.12.2016 bugfix group by-clause due to Forum #msg540610
  124. # 4.8.4 13.12.2016 added "group by ...,table_schema" to select in dbmeta_DoParse due to Forum #msg539228,
  125. # commandref adapted, changed "not_enough_data_in_period" to "less_data_in_period"
  126. # 4.8.3 12.12.2016 balance diff to next period if value of period is 0 between two periods with
  127. # values
  128. # 4.8.2 10.12.2016 bugfix negativ diff if balanced
  129. # 4.8.1 10.12.2016 added balance diff to diffValue, a difference between the last value of an
  130. # old aggregation period to the first value of a new aggregation period will be take over now
  131. # 4.8 09.12.2016 diffValue selection chenged to "between"
  132. # 4.7.7 08.12.2016 code review
  133. # 4.7.6 07.12.2016 DbRep version as internal, check if perl module DBI is installed
  134. # 4.7.5 05.12.2016 collaggstr day aggregation changed
  135. # 4.7.4 28.11.2016 sub calcount changed due to Forum #msg529312
  136. # 4.7.3 20.11.2016 new diffValue function made suitable to SQLite
  137. # 4.7.2 20.11.2016 commandref adapted, state = Warnings adapted
  138. # 4.7.1 17.11.2016 changed fieldlength to DbLog new standard, diffValue state Warnings due to
  139. # several situations and generate readings not_enough_data_in_period, diff-overrun_limit
  140. # 4.7 16.11.2016 sub diffValue changed due to Forum #msg520154, attr diffAccept added,
  141. # diffValue now able to calculate if counter was going to 0
  142. # 4.6.1 01.11.2016 daylight saving time check improved
  143. # 4.6 31.10.2016 bugfix calc issue due to daylight saving time end (winter time)
  144. # 4.5.1 18.10.2016 get svrinfo contains SQLite database file size (MB),
  145. # modified timeout routine
  146. # 4.5 17.10.2016 get data of dbstatus, dbvars, tableinfo, svrinfo (database dependend)
  147. # 4.4 13.10.2016 get function prepared
  148. # 4.3 11.10.2016 Preparation of get metadata
  149. # 4.2 10.10.2016 allow SQL-Wildcards (% _) in attr reading & attr device
  150. # 4.1.3 09.10.2016 bugfix delEntries running on SQLite
  151. # 4.1.2 08.10.2016 old device in DEF of connected DbLog device will substitute by renamed device if
  152. # it is present in DEF
  153. # 4.1.1 06.10.2016 NotifyFn is getting events from global AND own device, set is reduced if
  154. # ROLE=Agent, english commandref enhanced
  155. # 4.1 05.10.2016 DbRep_Attr changed
  156. # 4.0 04.10.2016 Internal/Attribute ROLE added, sub DbRep_firstconnect changed
  157. # NotifyFN activated to start deviceRename if ROLE=Agent
  158. # 3.13 03.10.2016 added deviceRename to rename devices in database, new Internal DATABASE
  159. # 3.12 02.10.2016 function minValue added
  160. # 3.11.1 30.09.2016 bugfix include first and next day in calculation if Timestamp is exactly 'YYYY-MM-DD 00:00:00'
  161. # 3.11 29.09.2016 maxValue calculation moved to background to reduce FHEM-load
  162. # 3.10.1 28.09.2016 sub impFile -> changed $dbh->{AutoCommit} = 0 to $dbh->begin_work
  163. # 3.10 27.09.2016 diffValue calculation moved to background to reduce FHEM-load,
  164. # new reading background_processing_time
  165. # 3.9.1 27.09.2016 Internal "LASTCMD" added
  166. # 3.9 26.09.2016 new function importFromFile to import data from file (CSV format)
  167. # 3.8 16.09.2016 new attr readingPreventFromDel to prevent readings from deletion
  168. # when a new operation starts
  169. # 3.7.3 11.09.2016 changed format of diffValue-reading if no value was selected
  170. # 3.7.2 04.09.2016 problem in diffValue fixed if if no value was selected
  171. # 3.7.1 31.08.2016 Reading "errortext" added, commandref continued, exportToFile changed,
  172. # diffValue changed to fix wrong timestamp if error occur
  173. # 3.7 30.08.2016 exportToFile added (exports data to file (CSV format)
  174. # 3.6 29.08.2016 plausibility checks of database column character length
  175. # 3.5.2 21.08.2016 fit to new commandref style
  176. # 3.5.1 20.08.2016 commandref continued
  177. # 3.5 18.08.2016 new attribute timeOlderThan
  178. # 3.4.4 12.08.2016 current_year_begin, previous_year_begin, current_year_end, previous_year_end
  179. # added as possible values for timestmp attribute
  180. # 3.4.3 09.08.2016 fields for input using "insert" changed to "date,time,value,unit". Attributes
  181. # device, reading will be used to complete dataset,
  182. # now more informations available about faulty datasets in arithmetic operations
  183. # 3.4.2 05.08.2016 commandref complemented, fieldlength used in function "insert" trimmed to 32
  184. # 3.4.1 04.08.2016 check of numeric value type in functions maxvalue, diffvalue
  185. # 3.4 03.08.2016 function "insert" added
  186. # 3.3.3 16.07.2016 bugfix of aggregation=week if month start is 01 and month end is 12 AND
  187. # the last week of december is "01" like in 2014 (checked in version 11804)
  188. # 3.3.2 16.07.2016 readings completed with begin of selection range to ensure valid reading order,
  189. # also done if readingNameMap is set
  190. # 3.3.1 15.07.2016 function "diffValue" changed, write "-" if no value
  191. # 3.3 12.07.2016 function "diffValue" added
  192. # 3.2.1 12.07.2016 DbRep_Notify prepared, switched from readingsSingleUpdate to readingsBulkUpdate
  193. # 3.2 11.07.2016 handling of db-errors is relocated to blockingcall-subs (checked in version 11785)
  194. # 3.1.1 10.07.2016 state turns to initialized and connected after attr "disabled" is switched from "1" to "0"
  195. # 3.1 09.07.2016 new Attr "timeDiffToNow" and change subs according to that
  196. # 3.0 04.07.2016 no selection if timestamp isn't set and aggregation isn't set with fetchrows, delEntries
  197. # 2.9.9 03.07.2016 english version of commandref completed
  198. # 2.9.8 01.07.2016 changed fetchrows_ParseDone to handle readingvalues with whitespaces correctly
  199. # 2.9.7 30.06.2016 moved {DBLOGDEVICE} to {HELPER}{DBLOGDEVICE}
  200. # 2.9.6 30.06.2016 sql-call changed for countEntries, averageValue, sumValue avoiding
  201. # problems if no timestamp is set and aggregation is set
  202. # 2.9.5 30.06.2016 format of readingnames changed again (substitute ":" with "-" in time)
  203. # 2.9.4 30.06.2016 change readingmap to readingNameMap, prove of unsupported characters added
  204. # 2.9.3 27.06.2016 format of readingnames changed avoiding some problems after restart and splitting
  205. # 2.9.2 27.06.2016 use Time::Local added, DbRep_firstconnect added
  206. # 2.9.1 26.06.2016 german commandref added
  207. # 2.9 25.06.2016 attributes showproctime, timeout added
  208. # 2.8.1 24.06.2016 sql-creation of sumValue, maxValue, fetchrows changed
  209. # main-routine changed
  210. # 2.8 24.06.2016 function averageValue changed to nonblocking function
  211. # 2.7.1 24.06.2016 changed blockingcall routines, changed to unique abort-function
  212. # 2.7 23.06.2016 changed function countEntries to nonblocking
  213. # 2.6.3 22.06.2016 abort-routines changed, dbconnect-routines changed
  214. # 2.6.2 21.06.2016 aggregation week corrected
  215. # 2.6.1 20.06.2016 routine maxval_ParseDone corrected
  216. # 2.6 31.05.2016 maxValue changed to nonblocking function
  217. # 2.5.3 31.05.2016 function delEntries changed
  218. # 2.5.2 31.05.2016 ping check changed, DbRep_Connect changed
  219. # 2.5.1 30.05.2016 sleep in nb-functions deleted
  220. # 2.5 30.05.2016 changed to use own $dbh with DbLog-credentials, function sumValue, fetchrows
  221. # 2.4.2 29.05.2016 function sumValue changed
  222. # 2.4.1 29.05.2016 function fetchrow changed
  223. # 2.4 29.05.2016 changed to nonblocking function for sumValue
  224. # 2.3 28.05.2016 changed sumValue to "prepare" with placeholders
  225. # 2.2 27.05.2016 changed fetchrow and delEntries function to "prepare" with placeholders
  226. # added nonblocking function for delEntries
  227. # 2.1 25.05.2016 codechange
  228. # 2.0 24.05.2016 added nonblocking function for fetchrow
  229. # 1.2 21.05.2016 function and attribute for delEntries added
  230. # 1.1 20.05.2016 change result-format of "count", move runtime-counter to sub collaggstr
  231. # 1.0 19.05.2016 Initial
  232. #
  233. package main;
  234. use strict;
  235. use warnings;
  236. use POSIX qw(strftime);
  237. use Time::HiRes qw(gettimeofday tv_interval);
  238. use Scalar::Util qw(looks_like_number);
  239. eval "use DBI;1" or my $DbRepMMDBI = "DBI";
  240. use DBI::Const::GetInfoType;
  241. use Blocking;
  242. use Time::Local;
  243. use Encode qw(encode_utf8);
  244. # no if $] >= 5.017011, warnings => 'experimental';
  245. sub DbRep_Main($$;$);
  246. my $DbRepVersion = "6.0.0";
  247. my %dbrep_col = ("DEVICE" => 64,
  248. "TYPE" => 64,
  249. "EVENT" => 512,
  250. "READING" => 64,
  251. "VALUE" => 128,
  252. "UNIT" => 32
  253. );
  254. ###################################################################################
  255. # DbRep_Initialize
  256. ###################################################################################
  257. sub DbRep_Initialize($) {
  258. my ($hash) = @_;
  259. $hash->{DefFn} = "DbRep_Define";
  260. $hash->{UndefFn} = "DbRep_Undef";
  261. $hash->{NotifyFn} = "DbRep_Notify";
  262. $hash->{SetFn} = "DbRep_Set";
  263. $hash->{GetFn} = "DbRep_Get";
  264. $hash->{AttrFn} = "DbRep_Attr";
  265. $hash->{FW_deviceOverview} = 1;
  266. $hash->{AttrList} = "disable:1,0 ".
  267. "reading ".
  268. "allowDeletion:1,0 ".
  269. "device " .
  270. "dumpComment ".
  271. "dumpDirLocal ".
  272. "dumpDirRemote ".
  273. "dumpMemlimit ".
  274. "dumpSpeed ".
  275. "dumpFilesKeep:1,2,3,4,5,6,7,8,9,10 ".
  276. "executeBeforeDump ".
  277. "executeAfterDump ".
  278. "expimpfile ".
  279. "ftpUse:1,0 ".
  280. "ftpUser ".
  281. "ftpUseSSL:1,0 ".
  282. "ftpDebug:1,0 ".
  283. "ftpDir ".
  284. "ftpPassive:1,0 ".
  285. "ftpPwd ".
  286. "ftpPort ".
  287. "ftpServer ".
  288. "ftpTimeout ".
  289. "aggregation:hour,day,week,month,no ".
  290. "diffAccept ".
  291. "limit ".
  292. "optimizeTablesBeforeDump:1,0 ".
  293. "readingNameMap ".
  294. "readingPreventFromDel ".
  295. "role:Client,Agent ".
  296. "showproctime:1,0 ".
  297. "showSvrInfo ".
  298. "showVariables ".
  299. "showStatus ".
  300. "showTableInfo ".
  301. "sqlResultFormat:separated,mline,sline,table,json ".
  302. "timestamp_begin ".
  303. "timestamp_end ".
  304. "timeDiffToNow ".
  305. "timeOlderThan ".
  306. "timeout ".
  307. "userExitFn ".
  308. $readingFnAttributes;
  309. return undef;
  310. }
  311. ###################################################################################
  312. # DbRep_Define
  313. ###################################################################################
  314. sub DbRep_Define($@) {
  315. # define <name> DbRep <DbLog-Device>
  316. # ($hash) [1] [2]
  317. #
  318. my ($hash, $def) = @_;
  319. my $name = $hash->{NAME};
  320. return "Error: Perl module ".$DbRepMMDBI." is missing. Install it on Debian with: sudo apt-get install libdbi-perl" if($DbRepMMDBI);
  321. my @a = split("[ \t][ \t]*", $def);
  322. if(int(@a) < 2) {
  323. return "You need to specify more parameters.\n". "Format: define <name> DbRep <DbLog-Device> <Reading> <Timestamp-Begin> <Timestamp-Ende>";
  324. }
  325. $hash->{LASTCMD} = " ";
  326. $hash->{ROLE} = AttrVal($name, "role", "Client");
  327. $hash->{HELPER}{DBLOGDEVICE} = $a[2];
  328. $hash->{VERSION} = $DbRepVersion;
  329. $hash->{NOTIFYDEV} = "global,".$name; # nur Events dieser Devices an DbRep_Notify weiterleiten
  330. my $dbconn = $defs{$a[2]}{dbconn};
  331. $hash->{DATABASE} = (split(/;|=/, $dbconn))[1];
  332. $hash->{UTF8} = defined($defs{$a[2]}{UTF8})?$defs{$a[2]}{UTF8}:0;
  333. RemoveInternalTimer($hash);
  334. InternalTimer(time+5, 'DbRep_firstconnect', $hash, 0);
  335. Log3 ($name, 4, "DbRep $name - initialized");
  336. ReadingsSingleUpdateValue ($hash, 'state', 'initialized', 1);
  337. return undef;
  338. }
  339. ###################################################################################
  340. # DbRep_Set
  341. ###################################################################################
  342. sub DbRep_Set($@) {
  343. my ($hash, @a) = @_;
  344. return "\"set X\" needs at least an argument" if ( @a < 2 );
  345. my $name = $a[0];
  346. my $opt = $a[1];
  347. my $prop = $a[2];
  348. my $dbh = $hash->{DBH};
  349. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  350. $hash->{dbloghash} = $defs{$dblogdevice};
  351. my $dbmodel = $hash->{dbloghash}{MODEL};
  352. my $dbname = $hash->{DATABASE};
  353. my (@bkps,$dir);
  354. $dir = AttrVal($name, "dumpDirLocal", "./"); # 'dumpDirRemote' (Backup-Verz. auf dem MySQL-Server) muß gemountet sein und in 'dumpDirLocal' eingetragen sein
  355. $dir = $dir."/" unless($dir =~ m/\/$/);
  356. opendir(DIR,$dir);
  357. my $sd = $dbname."_history_.*.csv";
  358. while (my $file = readdir(DIR)) {
  359. next unless (-f "$dir/$file");
  360. next unless ($file =~ /^$sd/);
  361. push @bkps,$file;
  362. }
  363. closedir(DIR);
  364. my $cj = @bkps?join(",",reverse(sort @bkps)):" ";
  365. my $setlist = "Unknown argument $opt, choose one of ".
  366. (($hash->{ROLE} ne "Agent")?"sumValue:noArg ":"").
  367. (($hash->{ROLE} ne "Agent")?"averageValue:noArg ":"").
  368. (($hash->{ROLE} ne "Agent")?"delEntries:noArg ":"").
  369. "deviceRename ".
  370. (($hash->{ROLE} ne "Agent")?"readingRename ":"").
  371. (($hash->{ROLE} ne "Agent")?"exportToFile:noArg ":"").
  372. (($hash->{ROLE} ne "Agent")?"importFromFile:noArg ":"").
  373. (($hash->{ROLE} ne "Agent")?"maxValue:noArg ":"").
  374. (($hash->{ROLE} ne "Agent")?"minValue:noArg ":"").
  375. (($hash->{ROLE} ne "Agent")?"fetchrows:history,current ":"").
  376. (($hash->{ROLE} ne "Agent")?"diffValue:noArg ":"").
  377. (($hash->{ROLE} ne "Agent")?"insert ":"").
  378. (($hash->{ROLE} ne "Agent")?"sqlCmd ":"").
  379. (($hash->{ROLE} ne "Agent")?"tableCurrentFillup:noArg ":"").
  380. (($hash->{ROLE} ne "Agent")?"tableCurrentPurge:noArg ":"").
  381. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"dumpMySQL:clientSide,serverSide ":"").
  382. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"optimizeTables:noArg ":"").
  383. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE|POSTGRESQL/ )?"vacuum:noArg ":"").
  384. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/)?"restoreMySQL:".$cj." ":"").
  385. (($hash->{ROLE} ne "Agent")?"countEntries:history,current ":"");
  386. return if(IsDisabled($name));
  387. if ($opt eq "dumpMySQL" && $hash->{ROLE} ne "Agent") {
  388. $hash->{LASTCMD} = "$opt";
  389. if ($prop eq "serverSide") {
  390. Log3 ($name, 3, "DbRep $name - ################################################################");
  391. Log3 ($name, 3, "DbRep $name - ### New database serverSide dump ###");
  392. Log3 ($name, 3, "DbRep $name - ################################################################");
  393. } else {
  394. Log3 ($name, 3, "DbRep $name - ################################################################");
  395. Log3 ($name, 3, "DbRep $name - ### New database clientSide dump ###");
  396. Log3 ($name, 3, "DbRep $name - ################################################################");
  397. }
  398. # Befehl vor Dump ausführen
  399. my $ebd = AttrVal($name, "executeBeforeDump", undef);
  400. if($ebd) {
  401. Log3 ($name, 4, "DbRep $name - execute command before dump: '$ebd' ");
  402. my $err = AnalyzeCommandChain(undef, $ebd);
  403. if ($err) {
  404. Log3 ($name, 2, "DbRep $name - $err");
  405. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  406. ReadingsSingleUpdateValue ($hash, "state", "error - command before dump not successful", 1);
  407. return undef;
  408. }
  409. }
  410. DbRep_Main($hash,$opt,$prop);
  411. return undef;
  412. }
  413. if ($opt eq "restoreMySQL" && $hash->{ROLE} ne "Agent") {
  414. $hash->{LASTCMD} = "$opt";
  415. Log3 ($name, 3, "DbRep $name - ################################################################");
  416. Log3 ($name, 3, "DbRep $name - ### New database Restore/Recovery ###");
  417. Log3 ($name, 3, "DbRep $name - ################################################################");
  418. DbRep_Main($hash,$opt,$prop);
  419. return undef;
  420. }
  421. if ($opt =~ /optimizeTables|vacuum/ && $hash->{ROLE} ne "Agent") {
  422. $hash->{LASTCMD} = "$opt";
  423. Log3 ($name, 3, "DbRep $name - ################################################################");
  424. Log3 ($name, 3, "DbRep $name - ### New optimize table / vacuum execution ###");
  425. Log3 ($name, 3, "DbRep $name - ################################################################");
  426. DbRep_Main($hash,$opt);
  427. return undef;
  428. }
  429. if ($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) {
  430. $setlist = "Unknown argument $opt, choose one of ".
  431. (($hash->{ROLE} ne "Agent")?"cancelDump:noArg ":"");
  432. }
  433. if ($opt =~ /countEntries/ && $hash->{ROLE} ne "Agent") {
  434. my $table = $prop?$prop:"history";
  435. DbRep_Main($hash,$opt,$table);
  436. } elsif ($opt eq "cancelDump" && $hash->{ROLE} ne "Agent") {
  437. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  438. Log3 ($name, 3, "DbRep $name -> running Dump has been canceled");
  439. ReadingsSingleUpdateValue ($hash, "state", "Dump canceled", 1);
  440. } elsif ($opt eq "fetchrows" && $hash->{ROLE} ne "Agent") {
  441. my $table = $prop?$prop:"history";
  442. DbRep_Main($hash,$opt,$table);
  443. } elsif ($opt =~ m/(max|min|sum|average|diff)Value/ && $hash->{ROLE} ne "Agent") {
  444. if (!AttrVal($hash->{NAME}, "reading", "")) {
  445. return " The attribute reading to analyze is not set !";
  446. }
  447. DbRep_Main($hash,$opt);
  448. } elsif ($opt =~ m/delEntries|tableCurrentPurge/ && $hash->{ROLE} ne "Agent") {
  449. if (!AttrVal($hash->{NAME}, "allowDeletion", undef)) {
  450. return " Set attribute 'allowDeletion' if you want to allow deletion of any database entries. Use it with care !";
  451. }
  452. DbRep_Main($hash,$opt);
  453. } elsif ($opt =~ m/tableCurrentFillup/ && $hash->{ROLE} ne "Agent") {
  454. DbRep_Main($hash,$opt);
  455. } elsif ($opt eq "deviceRename") {
  456. my ($olddev, $newdev) = split(",",$prop);
  457. if (!$olddev || !$newdev) {return "Both entries \"old device name\", \"new device name\" are needed. Use \"set ... deviceRename olddevname,newdevname\" ";}
  458. $hash->{HELPER}{OLDDEV} = $olddev;
  459. $hash->{HELPER}{NEWDEV} = $newdev;
  460. $hash->{HELPER}{RENMODE} = "devren";
  461. DbRep_Main($hash,$opt);
  462. } elsif ($opt eq "readingRename") {
  463. my ($oldread, $newread) = split(",",$prop);
  464. if (!$oldread || !$newread) {return "Both entries \"old reading name\", \"new reading name\" are needed. Use \"set ... readingRename oldreadingname,newreadingname\" ";}
  465. $hash->{HELPER}{OLDREAD} = $oldread;
  466. $hash->{HELPER}{NEWREAD} = $newread;
  467. $hash->{HELPER}{RENMODE} = "readren";
  468. DbRep_Main($hash,$opt);
  469. } elsif ($opt eq "insert" && $hash->{ROLE} ne "Agent") {
  470. if ($prop) {
  471. if (!AttrVal($hash->{NAME}, "device", "") || !AttrVal($hash->{NAME}, "reading", "") ) {
  472. return "One or both of attributes \"device\", \"reading\" is not set. It's mandatory to set both to complete dataset for manual insert !";
  473. }
  474. # Attribute device & reading dürfen kein SQL-Wildcard % enthalten
  475. return "One or both of attributes \"device\", \"reading\" containing SQL wildcard \"%\". Wildcards are not allowed in function manual insert !"
  476. if(AttrVal($hash->{NAME},"device","") =~ m/%/ || AttrVal($hash->{NAME},"reading","") =~ m/%/ );
  477. my ($i_date, $i_time, $i_value, $i_unit) = split(",",$prop);
  478. if (!$i_date || !$i_time || !$i_value) {return "At least data for \"Date\", \"Time\" and \"Value\" is needed to insert. \"Unit\" is optional. Inputformat is 'YYYY-MM-DD,HH:MM:SS,<Value(32)>,<Unit(32)>' ";}
  479. unless ($i_date =~ /(\d{4})-(\d{2})-(\d{2})/) {return "Input for date is not valid. Use format YYYY-MM-DD !";}
  480. unless ($i_time =~ /(\d{2}):(\d{2}):(\d{2})/) {return "Input for time is not valid. Use format HH:MM:SS !";}
  481. my $i_timestamp = $i_date." ".$i_time;
  482. my ($yyyy, $mm, $dd, $hh, $min, $sec) = ($i_timestamp =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  483. eval { my $ts = timelocal($sec, $min, $hh, $dd, $mm-1, $yyyy-1900); };
  484. if ($@) {
  485. my @l = split (/at/, $@);
  486. return " Timestamp is out of range - $l[0]";
  487. }
  488. my $i_device = AttrVal($hash->{NAME}, "device", "");
  489. my $i_reading = AttrVal($hash->{NAME}, "reading", "");
  490. # Daten auf maximale Länge (entsprechend der Feldlänge in DbLog DB create-scripts) beschneiden wenn nicht SQLite
  491. if ($dbmodel ne 'SQLITE') {
  492. $i_device = substr($i_device,0, $dbrep_col{DEVICE});
  493. $i_reading = substr($i_reading,0, $dbrep_col{READING});
  494. $i_value = substr($i_value,0, $dbrep_col{VALUE});
  495. $i_unit = substr($i_unit,0, $dbrep_col{UNIT}) if($i_unit);
  496. }
  497. $hash->{HELPER}{I_TIMESTAMP} = $i_timestamp;
  498. $hash->{HELPER}{I_DEVICE} = $i_device;
  499. $hash->{HELPER}{I_READING} = $i_reading;
  500. $hash->{HELPER}{I_VALUE} = $i_value;
  501. $hash->{HELPER}{I_UNIT} = $i_unit;
  502. $hash->{HELPER}{I_TYPE} = my $i_type = "manual";
  503. $hash->{HELPER}{I_EVENT} = my $i_event = "manual";
  504. } else {
  505. return "Data to insert to table 'history' are needed like this pattern: 'Date,Time,Value,[Unit]'. \"Unit\" is optional. Spaces are not allowed !";
  506. }
  507. DbRep_Main($hash,$opt);
  508. } elsif ($opt eq "exportToFile" && $hash->{ROLE} ne "Agent") {
  509. if (!AttrVal($hash->{NAME}, "expimpfile", "")) {
  510. return "The attribute \"expimpfile\" (path and filename) has to be set for export to file !";
  511. }
  512. DbRep_Main($hash,$opt);
  513. } elsif ($opt eq "importFromFile" && $hash->{ROLE} ne "Agent") {
  514. if (!AttrVal($hash->{NAME}, "expimpfile", "")) {
  515. return "The attribute \"expimpfile\" (path and filename) has to be set for import from file !";
  516. }
  517. DbRep_Main($hash,$opt);
  518. } elsif ($opt eq "sqlCmd") {
  519. # Execute a generic sql command
  520. return "\"set $opt\" needs at least an argument" if ( @a < 3 );
  521. # remove arg 0, 1 to get SQL command
  522. shift @a;
  523. shift @a;
  524. my $sqlcmd = join( " ", @a );
  525. if ($sqlcmd =~ m/^\s*delete/is && !AttrVal($hash->{NAME}, "allowDeletion", undef)) {
  526. return " Attribute 'allowDeletion = 1' is needed for command '$sqlcmd'. Use it with care !";
  527. }
  528. DbRep_Main($hash,$opt,$sqlcmd);
  529. } else {
  530. return "$setlist";
  531. }
  532. $hash->{LASTCMD} = "$opt";
  533. return undef;
  534. }
  535. ###################################################################################
  536. # DbRep_Get
  537. ###################################################################################
  538. sub DbRep_Get($@) {
  539. my ($hash, @a) = @_;
  540. return "\"get X\" needs at least an argument" if ( @a < 2 );
  541. my $name = $a[0];
  542. my $opt = $a[1];
  543. my $prop = $a[2];
  544. my $dbh = $hash->{DBH};
  545. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  546. $hash->{dbloghash} = $defs{$dblogdevice};
  547. my $dbmodel = $hash->{dbloghash}{MODEL};
  548. my $to = AttrVal($name, "timeout", "86400");
  549. my $getlist = "Unknown argument $opt, choose one of ".
  550. "svrinfo:noArg ".
  551. (($dbmodel eq "MYSQL")?"dbstatus:noArg ":"").
  552. (($dbmodel eq "MYSQL")?"tableinfo:noArg ":"").
  553. (($dbmodel eq "MYSQL")?"procinfo:noArg ":"").
  554. (($dbmodel eq "MYSQL")?"dbvars:noArg ":"")
  555. ;
  556. return if(IsDisabled($name));
  557. if ($opt =~ /dbvars|dbstatus|tableinfo|procinfo/) {
  558. return "The operation \"$opt\" isn't available with database type $dbmodel" if ($dbmodel ne 'MYSQL');
  559. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  560. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  561. delread($hash); # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  562. $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "ParseAborted", $hash);
  563. } elsif ($opt eq "svrinfo") {
  564. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  565. delread($hash);
  566. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  567. $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "ParseAborted", $hash);
  568. }
  569. else
  570. {
  571. return "$getlist";
  572. }
  573. $hash->{LASTCMD} = "$opt";
  574. return undef;
  575. }
  576. ###################################################################################
  577. # DbRep_Attr
  578. ###################################################################################
  579. sub DbRep_Attr($$$$) {
  580. my ($cmd,$name,$aName,$aVal) = @_;
  581. my $hash = $defs{$name};
  582. $hash->{dbloghash} = $defs{$hash->{HELPER}{DBLOGDEVICE}};
  583. my $dbmodel = $hash->{dbloghash}{MODEL};
  584. my $do;
  585. # $cmd can be "del" or "set"
  586. # $name is device name
  587. # aName and aVal are Attribute name and value
  588. # nicht erlaubte / nicht setzbare Attribute wenn role = Agent
  589. my @agentnoattr = qw(aggregation
  590. allowDeletion
  591. dumpDirLocal
  592. reading
  593. readingNameMap
  594. readingPreventFromDel
  595. device
  596. diffAccept
  597. executeBeforeDump
  598. executeAfterDump
  599. expimpfile
  600. ftpUse
  601. ftpUser
  602. ftpUseSSL
  603. ftpDebug
  604. ftpDir
  605. ftpPassive
  606. ftpPort
  607. ftpPwd
  608. ftpServer
  609. ftpTimeout
  610. dumpMemlimit
  611. dumpComment
  612. dumpSpeed
  613. optimizeTablesBeforeDump
  614. timestamp_begin
  615. timestamp_end
  616. timeDiffToNow
  617. timeOlderThan
  618. sqlResultFormat
  619. );
  620. if ($aName eq "disable") {
  621. if($cmd eq "set") {
  622. $do = ($aVal) ? 1 : 0;
  623. }
  624. $do = 0 if($cmd eq "del");
  625. my $val = ($do == 1 ? "disabled" : "initialized");
  626. ReadingsSingleUpdateValue ($hash, "state", $val, 1);
  627. if ($do == 0) {
  628. RemoveInternalTimer($hash);
  629. InternalTimer(time+5, 'DbRep_firstconnect', $hash, 0);
  630. } else {
  631. my $dbh = $hash->{DBH};
  632. $dbh->disconnect() if($dbh);
  633. }
  634. }
  635. if ($cmd eq "set" && $hash->{ROLE} eq "Agent") {
  636. foreach (@agentnoattr) {
  637. return ("Attribute $aName is not usable due to role of $name is \"$hash->{ROLE}\" ") if ($_ eq $aName);
  638. }
  639. }
  640. if ($aName eq "readingPreventFromDel") {
  641. if($cmd eq "set") {
  642. if($aVal =~ / /) {return "Usage of $aName is wrong. Use a comma separated list of readings which are should prevent from deletion when a new selection starts.";}
  643. $hash->{HELPER}{RDPFDEL} = $aVal;
  644. } else {
  645. delete $hash->{HELPER}{RDPFDEL} if($hash->{HELPER}{RDPFDEL});
  646. }
  647. }
  648. if ($aName eq "userExitFn") {
  649. if($cmd eq "set") {
  650. if(!$aVal) {return "Usage of $aName is wrong. The function has to be specified as \"<UserExitFn> [reading:value]\" ";}
  651. my @a = split(/ /,$aVal,2);
  652. $hash->{HELPER}{USEREXITFN} = $a[0];
  653. $hash->{HELPER}{UEFN_REGEXP} = $a[1] if($a[1]);
  654. } else {
  655. delete $hash->{HELPER}{USEREXITFN} if($hash->{HELPER}{USEREXITFN});
  656. delete $hash->{HELPER}{UEFN_REGEXP} if($hash->{HELPER}{UEFN_REGEXP});
  657. }
  658. }
  659. if ($aName eq "role") {
  660. if($cmd eq "set") {
  661. if ($aVal eq "Agent") {
  662. # check ob bereits ein Agent für die angeschlossene Datenbank existiert -> DbRep-Device kann dann keine Agent-Rolle einnehmen
  663. foreach(devspec2array("TYPE=DbRep")) {
  664. my $devname = $_;
  665. next if($devname eq $name);
  666. my $devrole = $defs{$_}{ROLE};
  667. my $devdb = $defs{$_}{DATABASE};
  668. if ($devrole eq "Agent" && $devdb eq $hash->{DATABASE}) { return "There is already an Agent device: $devname defined for database $hash->{DATABASE} !"; }
  669. }
  670. # nicht erlaubte Attribute löschen falls gesetzt
  671. foreach (@agentnoattr) {
  672. delete($attr{$name}{$_});
  673. }
  674. $attr{$name}{icon} = "security";
  675. }
  676. $do = $aVal;
  677. } else {
  678. $do = "Client";
  679. }
  680. $hash->{ROLE} = $do;
  681. delete($attr{$name}{icon}) if($do eq "Client");
  682. }
  683. if ($cmd eq "set") {
  684. if ($aName eq "timestamp_begin" || $aName eq "timestamp_end") {
  685. my ($a,$b,$c) = split('_',$aVal);
  686. if ($a =~ /^current$|^previous$/ && $b =~ /^hour$|^day$|^week$|^month$|^year$/ && $c =~ /^begin$|^end$/) {
  687. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  688. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  689. return undef;
  690. }
  691. unless ($aVal =~ /(19[0-9][0-9]|2[0-9][0-9][0-9])-(0[1-9]|1[1-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]) (0[0-9])|1[1-9]|2[0-3]:([0-5][0-9]):([0-5][0-9])/)
  692. {return " The Value of $aName is not valid. Use format YYYY-MM-DD HH:MM:SS or one of \"current_[year|month|day|hour]_begin\",\"current_[year|month|day|hour]_end\", \"previous_[year|month|day|hour]_begin\", \"previous_[year|month|day|hour]_end\" !";}
  693. my ($yyyy, $mm, $dd, $hh, $min, $sec) = ($aVal =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  694. eval { my $epoch_seconds_begin = timelocal($sec, $min, $hh, $dd, $mm-1, $yyyy-1900); };
  695. if ($@) {
  696. my @l = split (/at/, $@);
  697. return " The Value of $aName is out of range - $l[0]";
  698. }
  699. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  700. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  701. }
  702. if ($aName =~ /ftpTimeout|timeout|diffAccept/) {
  703. unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
  704. }
  705. if ($aName eq "readingNameMap") {
  706. unless ($aVal =~ m/^[A-Za-z\d_\.-]+$/) { return " Unsupported character in $aName found. Use only A-Z a-z _ . -";}
  707. }
  708. if ($aName eq "timeDiffToNow") {
  709. unless ($aVal =~ /^[0-9]+$/) { return "The Value of $aName is not valid. Use only figures 0-9 without decimal places. It's the time (in seconds) before current time used as start of selection. Refer to commandref !";}
  710. delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
  711. delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
  712. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  713. }
  714. if ($aName eq "dumpMemlimit" || $aName eq "dumpSpeed") {
  715. unless ($aVal =~ /^[0-9]+$/) { return "The Value of $aName is not valid. Use only figures 0-9 without decimal places.";}
  716. my $dml = AttrVal($name, "dumpMemlimit", 100000);
  717. my $ds = AttrVal($name, "dumpSpeed", 10000);
  718. if($aName eq "dumpMemlimit") {
  719. unless($aVal >= (10 * $ds)) {return "The Value of $aName has to be at least '10 x dumpSpeed' ! ";}
  720. }
  721. if($aName eq "dumpSpeed") {
  722. unless($aVal <= ($dml / 10)) {return "The Value of $aName mustn't be greater than 'dumpMemlimit / 10' ! ";}
  723. }
  724. }
  725. if ($aName eq "timeOlderThan") {
  726. unless ($aVal =~ /^[0-9]+$/) { return "The Value of $aName is not valid. Use only figures 0-9 without decimal places. It's the time (in seconds) before current time used as end of selection. Refer to commandref !";}
  727. delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
  728. delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
  729. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  730. }
  731. if ($aName eq "ftpUse") {
  732. delete($attr{$name}{ftpUseSSL});
  733. }
  734. if ($aName eq "ftpUseSSL") {
  735. delete($attr{$name}{ftpUse});
  736. }
  737. if ($aName eq "reading" || $aName eq "device") {
  738. if ($dbmodel && $dbmodel ne 'SQLITE') {
  739. my $attrname = uc($aName);
  740. if ($dbmodel eq 'POSTGRESQL' && $aVal !~ m/,/) {
  741. return "Length of \"$aName\" is too big. Maximum length for database type $dbmodel is $dbrep_col{$attrname}" if(length($aVal) > $dbrep_col{$attrname});
  742. } elsif ($dbmodel eq 'MYSQL' && $aVal !~ m/,/) {
  743. return "Length of \"$aName\" is too big. Maximum length for database type $dbmodel is $dbrep_col{$attrname}" if(length($aVal) > $dbrep_col{$attrname});
  744. }
  745. }
  746. }
  747. }
  748. return undef;
  749. }
  750. ###################################################################################
  751. # DbRep_Notify Eventverarbeitung
  752. ###################################################################################
  753. sub DbRep_Notify($$) {
  754. # Es werden nur die Events von Geräten verarbeitet die im Hash $hash->{NOTIFYDEV} gelistet sind (wenn definiert).
  755. # Dadurch kann die Menge der Events verringert werden. In sub DbRep_Define angeben.
  756. # Beispiele:
  757. # $hash->{NOTIFYDEV} = "global";
  758. # $hash->{NOTIFYDEV} = "global,Definition_A,Definition_B";
  759. my ($own_hash, $dev_hash) = @_;
  760. my $myName = $own_hash->{NAME}; # Name des eigenen Devices
  761. my $devName = $dev_hash->{NAME}; # Device welches Events erzeugt hat
  762. return if(IsDisabled($myName)); # Return if the module is disabled
  763. my $events = deviceEvents($dev_hash,0);
  764. return if(!$events);
  765. foreach my $event (@{$events}) {
  766. $event = "" if(!defined($event));
  767. my @evl = split("[ \t][ \t]*", $event);
  768. # if ($devName = $myName && $evl[0] =~ /done/) {
  769. # InternalTimer(time+1, "browser_refresh", $own_hash, 0);
  770. # }
  771. if ($own_hash->{ROLE} eq "Agent") {
  772. # wenn Rolle "Agent" Verbeitung von RENAMED Events
  773. next if ($event !~ /RENAMED/);
  774. my $strucChanged;
  775. # altes in neues device in der DEF des angeschlossenen DbLog-device ändern (neues device loggen)
  776. my $dblog_name = $own_hash->{dbloghash}{NAME}; # Name des an den DbRep-Agenten angeschlossenen DbLog-Dev
  777. my $dblog_hash = $defs{$dblog_name};
  778. if ( $dblog_hash->{DEF} =~ m/( |\(|\|)$evl[1]( |\)|\||:)/ ) {
  779. $dblog_hash->{DEF} =~ s/$evl[1]/$evl[2]/;
  780. $dblog_hash->{REGEXP} =~ s/$evl[1]/$evl[2]/;
  781. # Definitionsänderung wurde vorgenommen
  782. $strucChanged = 1;
  783. Log3 ($myName, 3, "DbRep Agent $myName - $dblog_name substituted in DEF, old: \"$evl[1]\", new: \"$evl[2]\" ");
  784. }
  785. # DEVICE innerhalb angeschlossener Datenbank umbenennen
  786. Log3 ($myName, 4, "DbRep Agent $myName - Evt RENAMED rec - old device: $evl[1], new device: $evl[2] -> start deviceRename in DB: $own_hash->{DATABASE} ");
  787. $own_hash->{HELPER}{OLDDEV} = $evl[1];
  788. $own_hash->{HELPER}{NEWDEV} = $evl[2];
  789. $own_hash->{HELPER}{RENMODE} = "devren";
  790. DbRep_Main($own_hash,"deviceRename");
  791. # die Attribute "device" in allen DbRep-Devices mit der Datenbank = DB des Agenten von alten Device in neues Device ändern
  792. foreach(devspec2array("TYPE=DbRep")) {
  793. my $repname = $_;
  794. next if($_ eq $myName);
  795. my $repattrdevice = $attr{$_}{device};
  796. next if(!$repattrdevice);
  797. my $repdb = $defs{$_}{DATABASE};
  798. if ($repattrdevice eq $evl[1] && $repdb eq $own_hash->{DATABASE}) {
  799. $attr{$_}{device} = $evl[2];
  800. # Definitionsänderung wurde vorgenommen
  801. $strucChanged = 1;
  802. Log3 ($myName, 3, "DbRep Agent $myName - $_ attr device changed, old: \"$evl[1]\", new: \"$evl[2]\" ");
  803. }
  804. }
  805. # if ($strucChanged) {CommandSave("","")};
  806. }
  807. }
  808. return;
  809. }
  810. ###################################################################################
  811. # DbRep_Undef
  812. ###################################################################################
  813. sub DbRep_Undef($$) {
  814. my ($hash, $arg) = @_;
  815. RemoveInternalTimer($hash);
  816. my $dbh = $hash->{DBH};
  817. $dbh->disconnect() if(defined($dbh));
  818. BlockingKill($hash->{HELPER}{RUNNING_PID}) if (exists($hash->{HELPER}{RUNNING_PID}));
  819. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BACKUP_CLIENT}));
  820. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  821. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  822. return undef;
  823. }
  824. ###################################################################################
  825. # First Init DB Connect
  826. ###################################################################################
  827. sub DbRep_firstconnect($) {
  828. my ($hash)= @_;
  829. my $name = $hash->{NAME};
  830. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  831. $hash->{dbloghash} = $defs{$dblogdevice};
  832. my $dbconn = $hash->{dbloghash}{dbconn};
  833. if ($init_done == 1) {
  834. if ( !DbRep_Connect($hash) ) {
  835. Log3 ($name, 2, "DbRep $name - DB connect failed. Credentials of database $hash->{DATABASE} are valid and database reachable ?");
  836. ReadingsSingleUpdateValue ($hash, "state", "disconnected", 1);
  837. } else {
  838. Log3 ($name, 4, "DbRep $name - Connectiontest to db $dbconn successful");
  839. my $dbh = $hash->{DBH};
  840. $dbh->disconnect();
  841. }
  842. } else {
  843. RemoveInternalTimer($hash, "DbRep_firstconnect");
  844. InternalTimer(time+1, "DbRep_firstconnect", $hash, 0);
  845. }
  846. return;
  847. }
  848. ###################################################################################
  849. # DB Connect
  850. ###################################################################################
  851. sub DbRep_Connect($) {
  852. my ($hash)= @_;
  853. my $name = $hash->{NAME};
  854. my $dbloghash = $hash->{dbloghash};
  855. my $dbconn = $dbloghash->{dbconn};
  856. my $dbuser = $dbloghash->{dbuser};
  857. my $dblogname = $dbloghash->{NAME};
  858. my $dbpassword = $attr{"sec$dblogname"}{secret};
  859. my $dbh;
  860. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1 });};
  861. if(!$dbh) {
  862. RemoveInternalTimer($hash);
  863. Log3 ($name, 3, "DbRep $name - Connectiontest to database $dbconn with user $dbuser");
  864. ReadingsSingleUpdateValue ($hash, 'state', 'disconnected', 1);
  865. InternalTimer(time+5, 'DbRep_Connect', $hash, 0);
  866. Log3 ($name, 3, "DbRep $name - Waiting for database connection");
  867. return 0;
  868. }
  869. $hash->{DBH} = $dbh;
  870. ReadingsSingleUpdateValue ($hash, "state", "connected", 1);
  871. Log3 ($name, 3, "DbRep $name - connected");
  872. return 1;
  873. }
  874. ################################################################################################################
  875. # Hauptroutine
  876. ################################################################################################################
  877. sub DbRep_Main($$;$) {
  878. my ($hash,$opt,$prop) = @_;
  879. my $name = $hash->{NAME};
  880. my $to = AttrVal($name, "timeout", "86400");
  881. my $reading = AttrVal($name, "reading", "%");
  882. my $aggregation = AttrVal($name, "aggregation", "no"); # wichtig !! aggregation niemals "undef"
  883. my $device = AttrVal($name, "device", "%");
  884. # Entkommentieren für Testroutine im Vordergrund
  885. # testexit($hash);
  886. return if( ($hash->{HELPER}{RUNNING_BACKUP_CLIENT} ||
  887. $hash->{HELPER}{RUNNING_BCKPREST_SERVER}) && $opt !~ /dumpMySQL|restoreMySQL/ );
  888. # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  889. delread($hash);
  890. if ($opt =~ /dumpMySQL/) {
  891. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BACKUP_CLIENT}));
  892. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  893. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  894. if ($prop eq "serverSide") {
  895. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_DoDumpServerSide", "$name", "DumpDone", $to, "DumpAborted", $hash);
  896. ReadingsSingleUpdateValue ($hash, "state", "serverSide Dump is running - be patient and see Logfile !", 1);
  897. } else {
  898. $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("mysql_DoDumpClientSide", "$name", "DumpDone", $to, "DumpAborted", $hash);
  899. ReadingsSingleUpdateValue ($hash, "state", "clientSide Dump is running - be patient and see Logfile !", 1);
  900. }
  901. return;
  902. }
  903. if ($opt =~ /restoreMySQL/) {
  904. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  905. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_RestoreServerSide", "$name|$prop", "RestoreDone", $to, "RestoreAborted", $hash);
  906. ReadingsSingleUpdateValue ($hash, "state", "restore database is running - be patient and see Logfile !", 1);
  907. return;
  908. }
  909. if ($opt =~ /optimizeTables|vacuum/) {
  910. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  911. $hash->{HELPER}{RUNNING_OPTIMIZE} = BlockingCall("DbRep_optimizeTables", "$name", "OptimizeDone", $to, "OptimizeAborted", $hash);
  912. ReadingsSingleUpdateValue ($hash, "state", "optimize tables is running - be patient and see Logfile !", 1);
  913. return;
  914. }
  915. if (exists($hash->{HELPER}{RUNNING_PID}) && $hash->{ROLE} ne "Agent") {
  916. Log3 ($name, 3, "DbRep $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} will be killed now to start a new BlockingCall");
  917. BlockingKill($hash->{HELPER}{RUNNING_PID});
  918. }
  919. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  920. # only for this block because of warnings if details of readings are not set
  921. no warnings 'uninitialized';
  922. # Ausgaben und Zeitmanipulationen
  923. Log3 ($name, 4, "DbRep $name - -------- New selection --------- ");
  924. Log3 ($name, 4, "DbRep $name - Aggregation: $aggregation") if($opt !~ /tableCurrentPurge|tableCurrentFillup|fetchrows|insert/);
  925. Log3 ($name, 4, "DbRep $name - Command: $opt");
  926. # zentrales Timestamp-Array und Zeitgrenzen bereitstellen
  927. my ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts) = createTimeArray($hash,$aggregation,$opt);
  928. ##### Funktionsaufrufe #####
  929. if ($opt eq "sumValue") {
  930. $hash->{HELPER}{RUNNING_PID} = BlockingCall("sumval_DoParse", "$name§$device§$reading§$ts", "sumval_ParseDone", $to, "ParseAborted", $hash);
  931. } elsif ($opt =~ m/countEntries/) {
  932. my $table = $prop;
  933. $hash->{HELPER}{RUNNING_PID} = BlockingCall("count_DoParse", "$name§$table§$device§$reading§$ts", "count_ParseDone", $to, "ParseAborted", $hash);
  934. } elsif ($opt eq "averageValue") {
  935. $hash->{HELPER}{RUNNING_PID} = BlockingCall("averval_DoParse", "$name§$device§$reading§$ts", "averval_ParseDone", $to, "ParseAborted", $hash);
  936. } elsif ($opt eq "fetchrows") {
  937. my $table = $prop;
  938. $runtime_string_first = defined($epoch_seconds_begin) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
  939. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  940. $hash->{HELPER}{RUNNING_PID} = BlockingCall("fetchrows_DoParse", "$name|$table|$device|$reading|$runtime_string_first|$runtime_string_next", "fetchrows_ParseDone", $to, "ParseAborted", $hash);
  941. } elsif ($opt eq "exportToFile") {
  942. $runtime_string_first = defined($epoch_seconds_begin) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
  943. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  944. $hash->{HELPER}{RUNNING_PID} = BlockingCall("expfile_DoParse", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "expfile_ParseDone", $to, "ParseAborted", $hash);
  945. } elsif ($opt eq "importFromFile") {
  946. $hash->{HELPER}{RUNNING_PID} = BlockingCall("impfile_Push", "$name", "impfile_PushDone", $to, "ParseAborted", $hash);
  947. } elsif ($opt eq "maxValue") {
  948. $hash->{HELPER}{RUNNING_PID} = BlockingCall("maxval_DoParse", "$name§$device§$reading§$ts", "maxval_ParseDone", $to, "ParseAborted", $hash);
  949. } elsif ($opt eq "minValue") {
  950. $hash->{HELPER}{RUNNING_PID} = BlockingCall("minval_DoParse", "$name§$device§$reading§$ts", "minval_ParseDone", $to, "ParseAborted", $hash);
  951. } elsif ($opt eq "delEntries") {
  952. $runtime_string_first = defined($epoch_seconds_begin) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
  953. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  954. $hash->{HELPER}{RUNNING_PID} = BlockingCall("del_DoParse", "$name|history|$device|$reading|$runtime_string_first|$runtime_string_next", "del_ParseDone", $to, "ParseAborted", $hash);
  955. } elsif ($opt eq "tableCurrentPurge") {
  956. undef $runtime_string_first;
  957. undef $runtime_string_next;
  958. $hash->{HELPER}{RUNNING_PID} = BlockingCall("del_DoParse", "$name|current|$device|$reading|$runtime_string_first|$runtime_string_next", "del_ParseDone", $to, "ParseAborted", $hash);
  959. } elsif ($opt eq "tableCurrentFillup") {
  960. $runtime_string_first = defined($epoch_seconds_begin) ? strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin) : "1970-01-01 01:00:00";
  961. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  962. $hash->{HELPER}{RUNNING_PID} = BlockingCall("currentfillup_Push", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "currentfillup_Done", $to, "ParseAborted", $hash);
  963. } elsif ($opt eq "diffValue") {
  964. $hash->{HELPER}{RUNNING_PID} = BlockingCall("diffval_DoParse", "$name§$device§$reading§$ts", "diffval_ParseDone", $to, "ParseAborted", $hash);
  965. } elsif ($opt eq "insert") {
  966. $hash->{HELPER}{RUNNING_PID} = BlockingCall("insert_Push", "$name", "insert_Done", $to, "ParseAborted", $hash);
  967. } elsif ($opt eq "deviceRename" || $opt eq "readingRename") {
  968. $hash->{HELPER}{RUNNING_PID} = BlockingCall("devren_Push", "$name", "devren_Done", $to, "ParseAborted", $hash);
  969. } elsif ($opt eq "sqlCmd" ) {
  970. # Execute a generic sql command
  971. $hash->{HELPER}{RUNNING_PID} = BlockingCall("sqlCmd_DoParse", "$name|$opt|$runtime_string_first|$runtime_string_next|$prop", "sqlCmd_ParseDone", $to, "ParseAborted", $hash);
  972. }
  973. return;
  974. }
  975. ################################################################################################################
  976. # Create zentrales Timsstamp-Array
  977. ################################################################################################################
  978. sub createTimeArray($$$) {
  979. my ($hash,$aggregation,$opt) = @_;
  980. my $name = $hash->{NAME};
  981. # year als Jahre seit 1900
  982. # $mon als 0..11
  983. # $time = timelocal( $sec, $min, $hour, $mday, $mon, $year );
  984. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(); # Istzeit Ableitung
  985. ###############################################################################################
  986. # Auswertungszeit Beginn (String)
  987. # dynamische Berechnung von Startdatum/zeit aus current_xxx_begin / previous_xxx_begin
  988. # dynamische Berechnung von Endedatum/zeit aus current_xxx_end / previous_xxx_end
  989. ###############################################################################################
  990. my ($tsbegin,$tsend,$dim,$tsub,$tadd);
  991. my ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear);
  992. $tsbegin = AttrVal($hash->{NAME}, "timestamp_begin", "1970-01-01 01:00:00");
  993. $tsend = AttrVal($hash->{NAME}, "timestamp_end", strftime "%Y-%m-%d %H:%M:%S", localtime(time));
  994. if (AttrVal($hash->{NAME},"timestamp_begin","") eq "current_year_begin" ||
  995. AttrVal($hash->{NAME},"timestamp_end","") eq "current_year_begin") {
  996. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year)) if(AttrVal($hash->{NAME},"timestamp_begin","") eq "current_year_begin");
  997. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year)) if(AttrVal($hash->{NAME},"timestamp_end","") eq "current_year_begin");
  998. }
  999. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_year_end" ||
  1000. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_year_end") {
  1001. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year)) if(AttrVal($hash->{NAME},"timestamp_begin","") eq "current_year_end");
  1002. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year)) if(AttrVal($hash->{NAME},"timestamp_end","") eq "current_year_end");
  1003. }
  1004. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_year_begin" ||
  1005. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_year_begin") {
  1006. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year-1)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_year_begin");
  1007. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year-1)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_year_begin");
  1008. }
  1009. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_year_end" ||
  1010. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_year_end") {
  1011. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year-1)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_year_end");
  1012. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year-1)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_year_end");
  1013. }
  1014. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_month_begin" ||
  1015. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_month_begin") {
  1016. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_month_begin");
  1017. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_month_begin");
  1018. }
  1019. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_month_end" ||
  1020. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_month_end") {
  1021. $dim = $mon-1?30+(($mon+1)*3%7<4):28+!($year%4||$year%400*!($year%100));
  1022. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_month_end");
  1023. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_month_end");
  1024. }
  1025. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_month_begin" ||
  1026. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_month_begin") {
  1027. $ryear = ($mon-1<0)?$year-1:$year;
  1028. $rmon = ($mon-1<0)?12:$mon-1;
  1029. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_month_begin");
  1030. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_month_begin");
  1031. }
  1032. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_month_end" ||
  1033. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_month_end") {
  1034. $ryear = ($mon-1<0)?$year-1:$year;
  1035. $rmon = ($mon-1<0)?12:$mon-1;
  1036. $dim = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100));
  1037. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_month_end");
  1038. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_month_end");
  1039. }
  1040. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_week_begin" ||
  1041. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_week_begin") {
  1042. $tsub = 0 if($wday == 1); # wenn Start am "Mo" keine Korrektur
  1043. $tsub = 86400 if($wday == 2); # wenn Start am "Di" dann Korrektur -1 Tage
  1044. $tsub = 172800 if($wday == 3); # wenn Start am "Mi" dann Korrektur -2 Tage
  1045. $tsub = 259200 if($wday == 4); # wenn Start am "Do" dann Korrektur -3 Tage
  1046. $tsub = 345600 if($wday == 5); # wenn Start am "Fr" dann Korrektur -4 Tage
  1047. $tsub = 432000 if($wday == 6); # wenn Start am "Sa" dann Korrektur -5 Tage
  1048. $tsub = 518400 if($wday == 0); # wenn Start am "So" dann Korrektur -6 Tage
  1049. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1050. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_week_begin");
  1051. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_week_begin");
  1052. }
  1053. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_week_end" ||
  1054. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_week_end") {
  1055. $tadd = 518400 if($wday == 1); # wenn Start am "Mo" dann Korrektur +6 Tage
  1056. $tadd = 432000 if($wday == 2); # wenn Start am "Di" dann Korrektur +5 Tage
  1057. $tadd = 345600 if($wday == 3); # wenn Start am "Mi" dann Korrektur +4 Tage
  1058. $tadd = 259200 if($wday == 4); # wenn Start am "Do" dann Korrektur +3 Tage
  1059. $tadd = 172800 if($wday == 5); # wenn Start am "Fr" dann Korrektur +2 Tage
  1060. $tadd = 86400 if($wday == 6); # wenn Start am "Sa" dann Korrektur +1 Tage
  1061. $tadd = 0 if($wday == 0); # wenn Start am "So" keine Korrektur
  1062. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time+$tadd);
  1063. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_week_end");
  1064. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_week_end");
  1065. }
  1066. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_week_begin" ||
  1067. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_week_begin") {
  1068. $tsub = 604800 if($wday == 1); # wenn Start am "Mo" dann Korrektur -7 Tage
  1069. $tsub = 691200 if($wday == 2); # wenn Start am "Di" dann Korrektur -8 Tage
  1070. $tsub = 777600 if($wday == 3); # wenn Start am "Mi" dann Korrektur -9 Tage
  1071. $tsub = 864000 if($wday == 4); # wenn Start am "Do" dann Korrektur -10 Tage
  1072. $tsub = 950400 if($wday == 5); # wenn Start am "Fr" dann Korrektur -11 Tage
  1073. $tsub = 1036800 if($wday == 6); # wenn Start am "Sa" dann Korrektur -12 Tage
  1074. $tsub = 1123200 if($wday == 0); # wenn Start am "So" dann Korrektur -13 Tage
  1075. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1076. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_week_begin");
  1077. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_week_begin");
  1078. }
  1079. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_week_end" ||
  1080. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_week_end") {
  1081. $tsub = 86400 if($wday == 1); # wenn Start am "Mo" dann Korrektur -1 Tage
  1082. $tsub = 172800 if($wday == 2); # wenn Start am "Di" dann Korrektur -2 Tage
  1083. $tsub = 259200 if($wday == 3); # wenn Start am "Mi" dann Korrektur -3 Tage
  1084. $tsub = 345600 if($wday == 4); # wenn Start am "Do" dann Korrektur -4 Tage
  1085. $tsub = 432000 if($wday == 5); # wenn Start am "Fr" dann Korrektur -5 Tage
  1086. $tsub = 518400 if($wday == 6); # wenn Start am "Sa" dann Korrektur -6 Tage
  1087. $tsub = 604800 if($wday == 0); # wenn Start am "So" dann Korrektur -7 Tage
  1088. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1089. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_week_end");
  1090. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_week_end");
  1091. }
  1092. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_day_begin" ||
  1093. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_day_begin") {
  1094. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_day_begin");
  1095. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_day_begin");
  1096. }
  1097. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_day_end" ||
  1098. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_day_end") {
  1099. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_day_end");
  1100. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_day_end");
  1101. }
  1102. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_day_begin" ||
  1103. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_day_begin") {
  1104. $rmday = $mday-1;
  1105. $rmon = $mon;
  1106. $ryear = $year;
  1107. if($rmday<1) {
  1108. $rmon--;
  1109. if ($rmon<0) {
  1110. $rmon=12;
  1111. $ryear--;
  1112. }
  1113. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1114. }
  1115. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_day_begin");
  1116. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_day_begin");
  1117. }
  1118. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_day_end" ||
  1119. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_day_end") {
  1120. $rmday = $mday-1;
  1121. $rmon = $mon;
  1122. $ryear = $year;
  1123. if($rmday<1) {
  1124. $rmon--;
  1125. if ($rmon<0) {
  1126. $rmon=12;
  1127. $ryear--;
  1128. }
  1129. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1130. }
  1131. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_day_end");
  1132. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_day_end");
  1133. }
  1134. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_hour_begin" ||
  1135. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_hour_begin") {
  1136. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$hour,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_hour_begin");
  1137. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$hour,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_hour_begin");
  1138. }
  1139. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_hour_end" ||
  1140. AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_hour_end") {
  1141. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$hour,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "current_hour_end");
  1142. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$hour,$mday,$mon,$year)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "current_hour_end");
  1143. }
  1144. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_hour_begin" ||
  1145. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_hour_begin") {
  1146. $rhour = $hour-1;
  1147. $rmday = $mday;
  1148. $rmon = $mon;
  1149. $ryear = $year;
  1150. if($rhour<0) {
  1151. $rhour = 23;
  1152. $rmday = $mday-1;
  1153. if($rmday<1) {
  1154. $rmon--;
  1155. if ($rmon<0) {
  1156. $rmon=12;
  1157. $ryear--;
  1158. }
  1159. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1160. }
  1161. }
  1162. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_hour_begin");
  1163. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_hour_begin");
  1164. }
  1165. if (AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_hour_end" ||
  1166. AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_hour_end") {
  1167. $rhour = $hour-1;
  1168. $rmday = $mday;
  1169. $rmon = $mon;
  1170. $ryear = $year;
  1171. if($rhour<0) {
  1172. $rhour = 23;
  1173. $rmday = $mday-1;
  1174. if($rmday<1) {
  1175. $rmon--;
  1176. if ($rmon<0) {
  1177. $rmon=12;
  1178. $ryear--;
  1179. }
  1180. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1181. }
  1182. }
  1183. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_begin", "") eq "previous_hour_end");
  1184. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($hash->{NAME}, "timestamp_end", "") eq "previous_hour_end");
  1185. }
  1186. # extrahieren der Einzelwerte von Datum/Zeit Beginn
  1187. my ($yyyy1, $mm1, $dd1, $hh1, $min1, $sec1) = ($tsbegin =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  1188. # extrahieren der Einzelwerte von Datum/Zeit Ende
  1189. my ($yyyy2, $mm2, $dd2, $hh2, $min2, $sec2) = ($tsend =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  1190. # Umwandeln in Epochesekunden Beginn
  1191. my $epoch_seconds_begin = timelocal($sec1, $min1, $hh1, $dd1, $mm1-1, $yyyy1-1900) if($tsbegin);
  1192. if(AttrVal($hash->{NAME}, "timeDiffToNow", undef)) {
  1193. $epoch_seconds_begin = time() - AttrVal($hash->{NAME}, "timeDiffToNow", undef);
  1194. Log3 ($name, 4, "DbRep $name - Time difference to current time for calculating Timestamp begin: ".AttrVal($hash->{NAME}, "timeDiffToNow", undef)." sec");
  1195. } elsif (AttrVal($hash->{NAME}, "timeOlderThan", undef)) {
  1196. $epoch_seconds_begin = timelocal(00, 00, 01, 01, 01-1, 1970-1900);
  1197. }
  1198. my $tsbegin_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin);
  1199. Log3 ($name, 5, "DbRep $name - Timestamp begin epocheseconds: $epoch_seconds_begin") if($opt !~ /tableCurrentPurge/);
  1200. Log3 ($name, 4, "DbRep $name - Timestamp begin human readable: $tsbegin_string") if($opt !~ /tableCurrentPurge/);
  1201. # Umwandeln in Epochesekunden Endezeit
  1202. my $epoch_seconds_end = timelocal($sec2, $min2, $hh2, $dd2, $mm2-1, $yyyy2-1900);
  1203. $epoch_seconds_end = AttrVal($hash->{NAME}, "timeOlderThan", undef) ? (time() - AttrVal($hash->{NAME}, "timeOlderThan", undef)) : $epoch_seconds_end;
  1204. Log3 ($name, 4, "DbRep $name - Time difference to current time for calculating Timestamp end: ".AttrVal($hash->{NAME}, "timeOlderThan", undef)." sec") if(AttrVal($hash->{NAME}, "timeOlderThan", undef));
  1205. my $tsend_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1206. Log3 ($name, 5, "DbRep $name - Timestamp end epocheseconds: $epoch_seconds_end") if($opt !~ /tableCurrentPurge/);
  1207. Log3 ($name, 4, "DbRep $name - Timestamp end human readable: $tsend_string") if($opt !~ /tableCurrentPurge/);
  1208. # Erstellung Wertehash für "collaggstr"
  1209. my $runtime = $epoch_seconds_begin; # Schleifenlaufzeit auf Beginn der Zeitselektion setzen
  1210. my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
  1211. my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
  1212. my $runtime_string_next; # Datum/Zeit + Periode (Granularität) für Auswertungsende im SQL-Format
  1213. my $reading_runtime_string; # zusammengesetzter Readingname+Aggregation für Update
  1214. my $tsstr = strftime "%H:%M:%S", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
  1215. my $testr = strftime "%H:%M:%S", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
  1216. my $dsstr = strftime "%Y-%m-%d", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
  1217. my $destr = strftime "%Y-%m-%d", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
  1218. my $msstr = strftime "%m", localtime($runtime); # Startmonat für Berechnung Monatsverschieber
  1219. my $mestr = strftime "%m", localtime($epoch_seconds_end); # Endemonat für Berechnung Monatsverschieber
  1220. my $ysstr = strftime "%Y", localtime($runtime); # Startjahr für Berechnung Monatsverschieber
  1221. my $yestr = strftime "%Y", localtime($epoch_seconds_end); # Endejahr für Berechnung Monatsverschieber
  1222. my $wd = strftime "%a", localtime($runtime); # Wochentag des aktuellen Startdatum/Zeit
  1223. my $wdadd = 604800 if($wd eq "Mo"); # wenn Start am "Mo" dann nächste Grenze +7 Tage
  1224. $wdadd = 518400 if($wd eq "Di"); # wenn Start am "Di" dann nächste Grenze +6 Tage
  1225. $wdadd = 432000 if($wd eq "Mi"); # wenn Start am "Mi" dann nächste Grenze +5 Tage
  1226. $wdadd = 345600 if($wd eq "Do"); # wenn Start am "Do" dann nächste Grenze +4 Tage
  1227. $wdadd = 259200 if($wd eq "Fr"); # wenn Start am "Fr" dann nächste Grenze +3 Tage
  1228. $wdadd = 172800 if($wd eq "Sa"); # wenn Start am "Sa" dann nächste Grenze +2 Tage
  1229. $wdadd = 86400 if($wd eq "So"); # wenn Start am "So" dann nächste Grenze +1 Tage
  1230. Log3 ($name, 5, "DbRep $name - weekday of start for selection: $wd -> wdadd: $wdadd");
  1231. my $aggsec;
  1232. if ($aggregation eq "hour") {
  1233. $aggsec = 3600;
  1234. } elsif ($aggregation eq "day") {
  1235. $aggsec = 86400;
  1236. } elsif ($aggregation eq "week") {
  1237. $aggsec = 604800;
  1238. } elsif ($aggregation eq "month") {
  1239. $aggsec = 2678400;
  1240. } elsif ($aggregation eq "no") {
  1241. $aggsec = 1;
  1242. } else {
  1243. return;
  1244. }
  1245. my %cv = (
  1246. tsstr => $tsstr,
  1247. testr => $testr,
  1248. dsstr => $dsstr,
  1249. destr => $destr,
  1250. msstr => $msstr,
  1251. mestr => $mestr,
  1252. ysstr => $ysstr,
  1253. yestr => $yestr,
  1254. aggsec => $aggsec,
  1255. aggregation => $aggregation,
  1256. epoch_seconds_end => $epoch_seconds_end,
  1257. wdadd => $wdadd
  1258. );
  1259. $hash->{HELPER}{CV} = \%cv;
  1260. my $ts; # für Erstellung Timestamp-Array zur nonblocking SQL-Abarbeitung
  1261. my $i = 1; # Schleifenzähler -> nur Indikator für ersten Durchlauf -> anderer $runtime_string_first
  1262. my $ll; # loopindikator, wenn 1 = loopausstieg
  1263. # Aufbau Timestampstring mit Zeitgrenzen entsprechend Aggregation
  1264. while (!$ll) {
  1265. # collect aggregation strings
  1266. ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll) = collaggstr($hash,$runtime,$i,$runtime_string_next);
  1267. $ts .= $runtime_string."#".$runtime_string_first."#".$runtime_string_next."|";
  1268. $i++;
  1269. }
  1270. return ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts);
  1271. }
  1272. ####################################################################################################
  1273. # Zusammenstellung Aggregationszeiträume
  1274. ####################################################################################################
  1275. sub collaggstr($$$$) {
  1276. my ($hash,$runtime,$i,$runtime_string_next) = @_;
  1277. my $name = $hash->{NAME};
  1278. my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
  1279. my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
  1280. my $ll; # loopindikator, wenn 1 = loopausstieg
  1281. my $runtime_orig; # orig. runtime als Grundlage für Addition mit $aggsec
  1282. my $tsstr = $hash->{HELPER}{CV}{tsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1283. my $testr = $hash->{HELPER}{CV}{testr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1284. my $dsstr = $hash->{HELPER}{CV}{dsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1285. my $destr = $hash->{HELPER}{CV}{destr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1286. my $msstr = $hash->{HELPER}{CV}{msstr}; # Startmonat für Berechnung Monatsverschieber
  1287. my $mestr = $hash->{HELPER}{CV}{mestr}; # Endemonat für Berechnung Monatsverschieber
  1288. my $ysstr = $hash->{HELPER}{CV}{ysstr}; # Startjahr für Berechnung Monatsverschieber
  1289. my $yestr = $hash->{HELPER}{CV}{yestr}; # Endejahr für Berechnung Monatsverschieber
  1290. my $aggregation = $hash->{HELPER}{CV}{aggregation}; # Aggregation
  1291. my $aggsec = $hash->{HELPER}{CV}{aggsec}; # laufende Aggregationssekunden
  1292. my $epoch_seconds_end = $hash->{HELPER}{CV}{epoch_seconds_end};
  1293. my $wdadd = $hash->{HELPER}{CV}{wdadd}; # Ergänzungstage. Starttag + Ergänzungstage = der folgende Montag (für week-Aggregation)
  1294. # only for this block because of warnings if some values not set
  1295. no warnings 'uninitialized';
  1296. # keine Aggregation (all between timestamps)
  1297. if ($aggregation eq "no") {
  1298. $runtime_string = "all_between_timestamps"; # für Readingname
  1299. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
  1300. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1301. $ll = 1;
  1302. }
  1303. # Monatsaggregation
  1304. if ($aggregation eq "month") {
  1305. $runtime_orig = $runtime;
  1306. $runtime = $runtime+3600 if(dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1307. # Hilfsrechnungen
  1308. my $rm = strftime "%m", localtime($runtime); # Monat des aktuell laufenden Startdatums d. SQL-Select
  1309. my $ry = strftime "%Y", localtime($runtime); # Jahr des aktuell laufenden Startdatums d. SQL-Select
  1310. my $dim = $rm-2?30+($rm*3%7<4):28+!($ry%4||$ry%400*!($ry%100)); # Anzahl Tage des aktuell laufenden Monats f. SQL-Select
  1311. Log3 ($name, 5, "DbRep $name - act year: $ry, act month: $rm, days in month: $dim, endyear: $yestr, endmonth: $mestr");
  1312. $runtime_string = strftime "%Y-%m", localtime($runtime); # für Readingname
  1313. if ($i==1) {
  1314. # nur im ersten Durchlauf
  1315. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime_orig);
  1316. }
  1317. if ($ysstr == $yestr && $msstr == $mestr || $ry == $yestr && $rm == $mestr) {
  1318. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
  1319. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1320. $ll=1;
  1321. } else {
  1322. if(($runtime) > $epoch_seconds_end) {
  1323. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>11);
  1324. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1325. $ll=1;
  1326. } else {
  1327. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
  1328. $runtime_string_next = strftime "%Y-%m-01", localtime($runtime+($dim*86400));
  1329. }
  1330. }
  1331. my ($yyyy1, $mm1, $dd1) = ($runtime_string_next =~ /(\d+)-(\d+)-(\d+)/);
  1332. $runtime = timelocal("00", "00", "00", "01", $mm1-1, $yyyy1-1900);
  1333. # neue Beginnzeit in Epoche-Sekunden
  1334. $runtime = $runtime_orig+$aggsec;
  1335. }
  1336. # Wochenaggregation
  1337. if ($aggregation eq "week") {
  1338. $runtime = $runtime+3600 if($i!=1 && dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1339. $runtime_orig = $runtime;
  1340. my $w = strftime "%V", localtime($runtime); # Wochennummer des aktuellen Startdatum/Zeit
  1341. $runtime_string = "week_".$w; # für Readingname
  1342. my $ms = strftime "%m", localtime($runtime); # Startmonat (01-12)
  1343. my $me = strftime "%m", localtime($epoch_seconds_end); # Endemonat (01-12)
  1344. if ($i==1) {
  1345. # nur im ersten Schleifendurchlauf
  1346. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
  1347. # Korrektur $runtime_orig für Berechnung neue Beginnzeit für nächsten Durchlauf
  1348. my ($yyyy1, $mm1, $dd1) = ($runtime_string_first =~ /(\d+)-(\d+)-(\d+)/);
  1349. $runtime = timelocal("00", "00", "00", $dd1, $mm1-1, $yyyy1-1900);
  1350. $runtime = $runtime+3600 if(dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1351. $runtime = $runtime+$wdadd;
  1352. $runtime_orig = $runtime-$aggsec;
  1353. # die Woche Beginn ist gleich der Woche vom Ende Auswertung
  1354. if((strftime "%V", localtime($epoch_seconds_end)) eq ($w) && ($ms+$me != 13)) {
  1355. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1356. $ll=1;
  1357. } else {
  1358. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime);
  1359. }
  1360. } else {
  1361. # weitere Durchläufe
  1362. if(($runtime+$aggsec) > $epoch_seconds_end) {
  1363. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig);
  1364. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1365. $ll=1;
  1366. } else {
  1367. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig) ;
  1368. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
  1369. }
  1370. }
  1371. # neue Beginnzeit in Epoche-Sekunden
  1372. $runtime = $runtime_orig+$aggsec;
  1373. }
  1374. # Tagesaggregation
  1375. if ($aggregation eq "day") {
  1376. $runtime_string = strftime "%Y-%m-%d", localtime($runtime); # für Readingname
  1377. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
  1378. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime) if($i>1);
  1379. $runtime = $runtime+3600 if(dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1380. if((($tsstr gt $testr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
  1381. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime);
  1382. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr);
  1383. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1384. $ll=1;
  1385. } else {
  1386. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
  1387. }
  1388. Log3 ($name, 5, "DbRep $name - runtime_string: $runtime_string, runtime_string_first(begin): $runtime_string_first, runtime_string_next(end): $runtime_string_next");
  1389. # neue Beginnzeit in Epoche-Sekunden
  1390. $runtime = $runtime+$aggsec;
  1391. }
  1392. # Stundenaggregation
  1393. if ($aggregation eq "hour") {
  1394. $runtime_string = strftime "%Y-%m-%d_%H", localtime($runtime); # für Readingname
  1395. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
  1396. $runtime = $runtime+3600 if(dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1397. $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime) if($i>1);
  1398. my @a = split (":",$tsstr);
  1399. my $hs = $a[0];
  1400. my $msstr = $a[1].":".$a[2];
  1401. @a = split (":",$testr);
  1402. my $he = $a[0];
  1403. my $mestr = $a[1].":".$a[2];
  1404. if((($msstr gt $mestr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
  1405. $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime);
  1406. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr && $hs eq $he);
  1407. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1408. $ll=1;
  1409. } else {
  1410. $runtime_string_next = strftime "%Y-%m-%d %H", localtime($runtime+$aggsec);
  1411. }
  1412. # neue Beginnzeit in Epoche-Sekunden
  1413. $runtime = $runtime+$aggsec;
  1414. }
  1415. return ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll);
  1416. }
  1417. ####################################################################################################
  1418. # nichtblockierende DB-Abfrage averageValue
  1419. ####################################################################################################
  1420. sub averval_DoParse($) {
  1421. my ($string) = @_;
  1422. my ($name, $device, $reading, $ts) = split("\\§", $string);
  1423. my $hash = $defs{$name};
  1424. my $dbloghash = $hash->{dbloghash};
  1425. my $dbconn = $dbloghash->{dbconn};
  1426. my $dbuser = $dbloghash->{dbuser};
  1427. my $dblogname = $dbloghash->{NAME};
  1428. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1429. my ($dbh,$sql,$sth,$err,$selspec);
  1430. # Background-Startzeit
  1431. my $bst = [gettimeofday];
  1432. Log3 ($name, 4, "DbRep $name -> Start BlockingCall averval_DoParse");
  1433. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  1434. if ($@) {
  1435. $err = encode_base64($@,"");
  1436. Log3 ($name, 2, "DbRep $name - $@");
  1437. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
  1438. return "$name|''|$device|$reading|''|$err";
  1439. }
  1440. # only for this block because of warnings if details of readings are not set
  1441. no warnings 'uninitialized';
  1442. # Timestampstring to Array
  1443. my @ts = split("\\|", $ts);
  1444. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  1445. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  1446. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  1447. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  1448. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  1449. if ($dbloghash->{MODEL} eq "POSTGRESQL") {
  1450. $selspec = "AVG(VALUE::numeric)";
  1451. } elsif ($dbloghash->{MODEL} eq "MYSQL") {
  1452. $selspec = "AVG(VALUE)";
  1453. } elsif ($dbloghash->{MODEL} eq "SQLITE") {
  1454. $selspec = "AVG(VALUE)";
  1455. } else {
  1456. $selspec = "AVG(VALUE)";
  1457. }
  1458. # SQL zusammenstellen für DB-Abfrage
  1459. if ($IsTimeSet || $IsAggrSet) {
  1460. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"?","?",'');
  1461. } else {
  1462. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,'');
  1463. }
  1464. eval{$sth = $dbh->prepare($sql);};
  1465. if ($@) {
  1466. $err = encode_base64($@,"");
  1467. Log3 ($name, 2, "DbRep $name - $@");
  1468. $dbh->disconnect;
  1469. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
  1470. return "$name|''|$device|$reading|''|$err";
  1471. }
  1472. # SQL-Startzeit
  1473. my $st = [gettimeofday];
  1474. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  1475. my $arrstr;
  1476. foreach my $row (@ts) {
  1477. my @a = split("#", $row);
  1478. my $runtime_string = $a[0];
  1479. my $runtime_string_first = $a[1];
  1480. my $runtime_string_next = $a[2];
  1481. # SQL zusammenstellen für Logging
  1482. if ($IsTimeSet || $IsAggrSet) {
  1483. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  1484. }
  1485. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  1486. if ($IsTimeSet || $IsAggrSet) {
  1487. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  1488. } else {
  1489. eval{$sth->execute();};
  1490. }
  1491. if ($@) {
  1492. $err = encode_base64($@,"");
  1493. Log3 ($name, 2, "DbRep $name - $@");
  1494. $dbh->disconnect;
  1495. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
  1496. return "$name|''|$device|$reading|''|$err";
  1497. }
  1498. # DB-Abfrage -> Ergebnis in @arr aufnehmen
  1499. my @line = $sth->fetchrow_array();
  1500. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  1501. if(AttrVal($name, "aggregation", "") eq "hour") {
  1502. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  1503. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  1504. } else {
  1505. my @rsf = split(" ",$runtime_string_first);
  1506. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  1507. }
  1508. }
  1509. $sth->finish;
  1510. $dbh->disconnect;
  1511. # SQL-Laufzeit ermitteln
  1512. my $rt = tv_interval($st);
  1513. # Daten müssen als Einzeiler zurückgegeben werden
  1514. $arrstr = encode_base64($arrstr,"");
  1515. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_DoParse finished");
  1516. # Background-Laufzeit ermitteln
  1517. my $brt = tv_interval($bst);
  1518. $rt = $rt.",".$brt;
  1519. return "$name|$arrstr|$device|$reading|$rt|0";
  1520. }
  1521. ####################################################################################################
  1522. # Auswertungsroutine der nichtblockierenden DB-Abfrage averageValue
  1523. ####################################################################################################
  1524. sub averval_ParseDone($) {
  1525. my ($string) = @_;
  1526. my @a = split("\\|",$string);
  1527. my $hash = $defs{$a[0]};
  1528. my $name = $hash->{NAME};
  1529. my $arrstr = decode_base64($a[1]);
  1530. my $device = $a[2];
  1531. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1532. my $reading = $a[3];
  1533. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1534. my $bt = $a[4];
  1535. my ($rt,$brt) = split(",", $bt);
  1536. my $err = $a[5]?decode_base64($a[5]):undef;
  1537. my $reading_runtime_string;
  1538. Log3 ($name, 4, "DbRep $name -> Start BlockingCall averval_ParseDone");
  1539. if ($err) {
  1540. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  1541. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  1542. delete($hash->{HELPER}{RUNNING_PID});
  1543. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_ParseDone finished");
  1544. return;
  1545. }
  1546. # only for this block because of warnings if details of readings are not set
  1547. no warnings 'uninitialized';
  1548. # Readingaufbereitung
  1549. readingsBeginUpdate($hash);
  1550. my @arr = split("\\|", $arrstr);
  1551. foreach my $row (@arr) {
  1552. my @a = split("#", $row);
  1553. my $runtime_string = $a[0];
  1554. my $c = $a[1];
  1555. my $rsf = $a[2]."__";
  1556. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  1557. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  1558. } else {
  1559. my $ds = $device."__" if ($device);
  1560. my $rds = $reading."__" if ($reading);
  1561. $reading_runtime_string = $rsf.$ds.$rds."AVERAGE__".$runtime_string;
  1562. }
  1563. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-");
  1564. }
  1565. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  1566. readingsEndUpdate($hash, 1);
  1567. delete($hash->{HELPER}{RUNNING_PID});
  1568. Log3 ($name, 4, "DbRep $name -> BlockingCall averval_ParseDone finished");
  1569. return;
  1570. }
  1571. ####################################################################################################
  1572. # nichtblockierende DB-Abfrage count
  1573. ####################################################################################################
  1574. sub count_DoParse($) {
  1575. my ($string) = @_;
  1576. my ($name,$table,$device,$reading,$ts) = split("\\§", $string);
  1577. my $hash = $defs{$name};
  1578. my $dbloghash = $hash->{dbloghash};
  1579. my $dbconn = $dbloghash->{dbconn};
  1580. my $dbuser = $dbloghash->{dbuser};
  1581. my $dblogname = $dbloghash->{NAME};
  1582. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1583. my ($dbh,$sql,$sth,$err,$selspec);
  1584. # Background-Startzeit
  1585. my $bst = [gettimeofday];
  1586. Log3 ($name, 4, "DbRep $name -> Start BlockingCall count_DoParse");
  1587. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  1588. if ($@) {
  1589. $err = encode_base64($@,"");
  1590. Log3 ($name, 2, "DbRep $name - $@");
  1591. Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
  1592. return "$name|''|$device|$reading|''|$err|$table";
  1593. }
  1594. # only for this block because of warnings if details of readings are not set
  1595. no warnings 'uninitialized';
  1596. # Timestampstring to Array
  1597. my @ts = split("\\|", $ts);
  1598. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  1599. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  1600. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  1601. # SQL zusammenstellen für DB-Abfrage
  1602. if ($IsTimeSet || $IsAggrSet) {
  1603. $sql = createSelectSql($hash,$table,"COUNT(*)",$device,$reading,"?","?",'');
  1604. } else {
  1605. $sql = createSelectSql($hash,$table,"COUNT(*)",$device,$reading,undef,undef,'');
  1606. }
  1607. eval{$sth = $dbh->prepare($sql);};
  1608. if ($@) {
  1609. $err = encode_base64($@,"");
  1610. Log3 ($name, 2, "DbRep $name - $@");
  1611. $dbh->disconnect;
  1612. Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
  1613. return "$name|''|$device|$reading|''|$err|$table";
  1614. }
  1615. # SQL-Startzeit
  1616. my $st = [gettimeofday];
  1617. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  1618. my $arrstr;
  1619. foreach my $row (@ts) {
  1620. my @a = split("#", $row);
  1621. my $runtime_string = $a[0];
  1622. my $runtime_string_first = $a[1];
  1623. my $runtime_string_next = $a[2];
  1624. # SQL zusammenstellen für Logging
  1625. if ($IsTimeSet || $IsAggrSet) {
  1626. $sql = createSelectSql($hash,$table,"COUNT(*)",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  1627. }
  1628. Log3($name, 4, "DbRep $name - SQL execute: $sql");
  1629. if ($IsTimeSet || $IsAggrSet) {
  1630. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  1631. } else {
  1632. eval{$sth->execute();};
  1633. }
  1634. if ($@) {
  1635. $err = encode_base64($@,"");
  1636. Log3 ($name, 2, "DbRep $name - $@");
  1637. $dbh->disconnect;
  1638. Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
  1639. return "$name|''|$device|$reading|''|$err|$table";
  1640. }
  1641. # DB-Abfrage -> Ergebnis in @arr aufnehmen
  1642. my @line = $sth->fetchrow_array();
  1643. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  1644. if(AttrVal($name, "aggregation", "") eq "hour") {
  1645. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  1646. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  1647. } else {
  1648. my @rsf = split(" ",$runtime_string_first);
  1649. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  1650. }
  1651. }
  1652. $sth->finish;
  1653. $dbh->disconnect;
  1654. # SQL-Laufzeit ermitteln
  1655. my $rt = tv_interval($st);
  1656. # Daten müssen als Einzeiler zurückgegeben werden
  1657. $arrstr = encode_base64($arrstr,"");
  1658. Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
  1659. # Background-Laufzeit ermitteln
  1660. my $brt = tv_interval($bst);
  1661. $rt = $rt.",".$brt;
  1662. return "$name|$arrstr|$device|$reading|$rt|0|$table";
  1663. }
  1664. ####################################################################################################
  1665. # Auswertungsroutine der nichtblockierenden DB-Abfrage count
  1666. ####################################################################################################
  1667. sub count_ParseDone($) {
  1668. my ($string) = @_;
  1669. my @a = split("\\|",$string);
  1670. my $hash = $defs{$a[0]};
  1671. my $name = $hash->{NAME};
  1672. my $arrstr = decode_base64($a[1]);
  1673. my $device = $a[2];
  1674. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1675. my $reading = $a[3];
  1676. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1677. my $bt = $a[4];
  1678. my ($rt,$brt) = split(",", $bt);
  1679. my $err = $a[5]?decode_base64($a[5]):undef;
  1680. my $table = $a[6];
  1681. my $reading_runtime_string;
  1682. Log3 ($name, 4, "DbRep $name -> Start BlockingCall count_ParseDone");
  1683. if ($err) {
  1684. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  1685. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  1686. delete($hash->{HELPER}{RUNNING_PID});
  1687. Log3 ($name, 4, "DbRep $name -> BlockingCall count_ParseDone finished");
  1688. return;
  1689. }
  1690. Log3 ($name, 5, "DbRep $name - SQL result decoded: $arrstr") if($arrstr);
  1691. # only for this block because of warnings if details of readings are not set
  1692. no warnings 'uninitialized';
  1693. # Readingaufbereitung
  1694. readingsBeginUpdate($hash);
  1695. my @arr = split("\\|", $arrstr);
  1696. foreach my $row (@arr) {
  1697. my @a = split("#", $row);
  1698. my $runtime_string = $a[0];
  1699. my $c = $a[1];
  1700. my $rsf = $a[2]."__";
  1701. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  1702. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  1703. } else {
  1704. my $ds = $device."__" if ($device);
  1705. my $rds = $reading."__" if ($reading);
  1706. $reading_runtime_string = $rsf.$ds.$rds."COUNT_".$table."__".$runtime_string;
  1707. }
  1708. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?$c:"-");
  1709. }
  1710. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  1711. readingsEndUpdate($hash, 1);
  1712. delete($hash->{HELPER}{RUNNING_PID});
  1713. Log3 ($name, 4, "DbRep $name -> BlockingCall count_ParseDone finished");
  1714. return;
  1715. }
  1716. ####################################################################################################
  1717. # nichtblockierende DB-Abfrage maxValue
  1718. ####################################################################################################
  1719. sub maxval_DoParse($) {
  1720. my ($string) = @_;
  1721. my ($name, $device, $reading, $ts) = split("\\§", $string);
  1722. my $hash = $defs{$name};
  1723. my $dbloghash = $hash->{dbloghash};
  1724. my $dbconn = $dbloghash->{dbconn};
  1725. my $dbuser = $dbloghash->{dbuser};
  1726. my $dblogname = $dbloghash->{NAME};
  1727. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1728. my ($dbh,$sql,$sth,$err);
  1729. # Background-Startzeit
  1730. my $bst = [gettimeofday];
  1731. Log3 ($name, 4, "DbRep $name -> Start BlockingCall maxval_DoParse");
  1732. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  1733. if ($@) {
  1734. $err = encode_base64($@,"");
  1735. Log3 ($name, 2, "DbRep $name - $@");
  1736. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
  1737. return "$name|''|$device|$reading|''|$err";
  1738. }
  1739. # only for this block because of warnings if details of readings are not set
  1740. no warnings 'uninitialized';
  1741. # Timestampstring to Array
  1742. my @ts = split("\\|", $ts);
  1743. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  1744. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  1745. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  1746. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  1747. # SQL zusammenstellen für DB-Operation
  1748. if ($IsTimeSet || $IsAggrSet) {
  1749. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"?","?","ORDER BY TIMESTAMP");
  1750. } else {
  1751. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,undef,undef,"ORDER BY TIMESTAMP");
  1752. }
  1753. eval{$sth = $dbh->prepare($sql);};
  1754. if ($@) {
  1755. $err = encode_base64($@,"");
  1756. Log3 ($name, 2, "DbRep $name - $@");
  1757. $dbh->disconnect;
  1758. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
  1759. return "$name|''|$device|$reading|''|$err";
  1760. }
  1761. # SQL-Startzeit
  1762. my $st = [gettimeofday];
  1763. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  1764. my @row_array;
  1765. foreach my $row (@ts) {
  1766. my @a = split("#", $row);
  1767. my $runtime_string = $a[0];
  1768. my $runtime_string_first = $a[1];
  1769. my $runtime_string_next = $a[2];
  1770. # SQL zusammenstellen für Logausgabe
  1771. if ($IsTimeSet || $IsAggrSet) {
  1772. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP");
  1773. }
  1774. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  1775. $runtime_string = encode_base64($runtime_string,"");
  1776. if ($IsTimeSet || $IsAggrSet) {
  1777. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  1778. } else {
  1779. eval{$sth->execute();};
  1780. }
  1781. if ($@) {
  1782. $err = encode_base64($@,"");
  1783. Log3 ($name, 2, "DbRep $name - $@");
  1784. $dbh->disconnect;
  1785. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
  1786. return "$name|''|$device|$reading|''|$err";
  1787. }
  1788. my @array= map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  1789. if(!@array) {
  1790. if(AttrVal($name, "aggregation", "") eq "hour") {
  1791. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  1792. @array = ($runtime_string." "."0"." ".$rsf[0]."_".$rsf[1]."\n");
  1793. } else {
  1794. my @rsf = split(" ",$runtime_string_first);
  1795. @array = ($runtime_string." "."0"." ".$rsf[0]."\n");
  1796. }
  1797. }
  1798. push(@row_array, @array);
  1799. }
  1800. $sth->finish;
  1801. $dbh->disconnect;
  1802. # SQL-Laufzeit ermitteln
  1803. my $rt = tv_interval($st);
  1804. Log3 ($name, 5, "DbRep $name -> raw data of row_array result:\n @row_array");
  1805. #---------- Berechnung Ergebnishash maxValue ------------------------
  1806. my $i = 1;
  1807. my %rh = ();
  1808. my $lastruntimestring;
  1809. my $row_max_time;
  1810. my $max_value = 0;
  1811. foreach my $row (@row_array) {
  1812. my @a = split("[ \t][ \t]*", $row);
  1813. my $runtime_string = decode_base64($a[0]);
  1814. $lastruntimestring = $runtime_string if ($i == 1);
  1815. my $value = $a[1];
  1816. $a[-1] =~ s/:/-/g if($a[-1]); # substituieren unsupported characters -> siehe fhem.pl
  1817. my $timestamp = ($a[-1]&&$a[-2])?$a[-2]."_".$a[-1]:$a[-1];
  1818. # Leerzeichen am Ende $timestamp entfernen
  1819. $timestamp =~ s/\s+$//g;
  1820. # Test auf $value = "numeric"
  1821. if (!looks_like_number($value)) {
  1822. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in maxValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  1823. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  1824. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
  1825. return "$name|''|$device|$reading|''|$err";
  1826. }
  1827. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, TIMESTAMP: $timestamp, VALUE: $value");
  1828. if ($runtime_string eq $lastruntimestring) {
  1829. if ($value >= $max_value) {
  1830. $max_value = $value;
  1831. $row_max_time = $timestamp;
  1832. $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
  1833. }
  1834. } else {
  1835. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen
  1836. $lastruntimestring = $runtime_string;
  1837. $max_value = 0;
  1838. if ($value >= $max_value) {
  1839. $max_value = $value;
  1840. $row_max_time = $timestamp;
  1841. $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
  1842. }
  1843. }
  1844. $i++;
  1845. }
  1846. #---------------------------------------------------------------------------------------------
  1847. Log3 ($name, 5, "DbRep $name - result of maxValue calculation before encoding:");
  1848. foreach my $key (sort(keys(%rh))) {
  1849. Log3 ($name, 5, "runtimestring Key: $key, value: ".$rh{$key});
  1850. }
  1851. # Ergebnishash als Einzeiler zurückgeben
  1852. my $rows = join('§', %rh);
  1853. my $rowlist = encode_base64($rows,"");
  1854. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_DoParse finished");
  1855. # Background-Laufzeit ermitteln
  1856. my $brt = tv_interval($bst);
  1857. $rt = $rt.",".$brt;
  1858. return "$name|$rowlist|$device|$reading|$rt|0";
  1859. }
  1860. ####################################################################################################
  1861. # Auswertungsroutine der nichtblockierenden DB-Abfrage maxValue
  1862. ####################################################################################################
  1863. sub maxval_ParseDone($) {
  1864. my ($string) = @_;
  1865. my @a = split("\\|",$string);
  1866. my $hash = $defs{$a[0]};
  1867. my $name = $hash->{NAME};
  1868. my $rowlist = decode_base64($a[1]);
  1869. my $device = $a[2];
  1870. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1871. my $reading = $a[3];
  1872. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  1873. my $bt = $a[4];
  1874. my ($rt,$brt) = split(",", $bt);
  1875. my $err = $a[5]?decode_base64($a[5]):undef;
  1876. my $reading_runtime_string;
  1877. Log3 ($name, 4, "DbRep $name -> Start BlockingCall maxval_ParseDone");
  1878. if ($err) {
  1879. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  1880. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  1881. delete($hash->{HELPER}{RUNNING_PID});
  1882. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_ParseDone finished");
  1883. return;
  1884. }
  1885. my %rh = split("§", $rowlist);
  1886. Log3 ($name, 5, "DbRep $name - result of maxValue calculation after decoding:");
  1887. foreach my $key (sort(keys(%rh))) {
  1888. Log3 ($name, 5, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  1889. }
  1890. # Readingaufbereitung
  1891. readingsBeginUpdate($hash);
  1892. # only for this block because of warnings if details of readings are not set
  1893. no warnings 'uninitialized';
  1894. foreach my $key (sort(keys(%rh))) {
  1895. my @k = split("\\|",$rh{$key});
  1896. my $rsf = $k[2]."__" if($k[2]);
  1897. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  1898. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  1899. } else {
  1900. my $ds = $device."__" if ($device);
  1901. my $rds = $reading."__" if ($reading);
  1902. $reading_runtime_string = $rsf.$ds.$rds."MAX__".$k[0];
  1903. }
  1904. my $rv = $k[1];
  1905. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, defined($rv)?sprintf("%.4f",$rv):"-");
  1906. }
  1907. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  1908. readingsEndUpdate($hash, 1);
  1909. delete($hash->{HELPER}{RUNNING_PID});
  1910. Log3 ($name, 4, "DbRep $name -> BlockingCall maxval_ParseDone finished");
  1911. return;
  1912. }
  1913. ####################################################################################################
  1914. # nichtblockierende DB-Abfrage minValue
  1915. ####################################################################################################
  1916. sub minval_DoParse($) {
  1917. my ($string) = @_;
  1918. my ($name, $device, $reading, $ts) = split("\\§", $string);
  1919. my $hash = $defs{$name};
  1920. my $dbloghash = $hash->{dbloghash};
  1921. my $dbconn = $dbloghash->{dbconn};
  1922. my $dbuser = $dbloghash->{dbuser};
  1923. my $dblogname = $dbloghash->{NAME};
  1924. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1925. my ($dbh,$sql,$sth,$err);
  1926. # Background-Startzeit
  1927. my $bst = [gettimeofday];
  1928. Log3 ($name, 4, "DbRep $name -> Start BlockingCall minval_DoParse");
  1929. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  1930. if ($@) {
  1931. $err = encode_base64($@,"");
  1932. Log3 ($name, 2, "DbRep $name - $@");
  1933. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_DoParse finished");
  1934. return "$name|''|$device|$reading|''|$err";
  1935. }
  1936. # only for this block because of warnings if details of readings are not set
  1937. no warnings 'uninitialized';
  1938. # Timestampstring to Array
  1939. my @ts = split("\\|", $ts);
  1940. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  1941. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  1942. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  1943. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  1944. # SQL zusammenstellen für DB-Operation
  1945. if ($IsTimeSet || $IsAggrSet) {
  1946. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"?","?","ORDER BY TIMESTAMP");
  1947. } else {
  1948. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,undef,undef,"ORDER BY TIMESTAMP");
  1949. }
  1950. eval{$sth = $dbh->prepare($sql);};
  1951. if ($@) {
  1952. $err = encode_base64($@,"");
  1953. Log3 ($name, 2, "DbRep $name - $@");
  1954. $dbh->disconnect;
  1955. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_DoParse finished");
  1956. return "$name|''|$device|$reading|''|$err";
  1957. }
  1958. # SQL-Startzeit
  1959. my $st = [gettimeofday];
  1960. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  1961. my @row_array;
  1962. foreach my $row (@ts) {
  1963. my @a = split("#", $row);
  1964. my $runtime_string = $a[0];
  1965. my $runtime_string_first = $a[1];
  1966. my $runtime_string_next = $a[2];
  1967. # SQL zusammenstellen für Logausgabe
  1968. if ($IsTimeSet || $IsAggrSet) {
  1969. $sql = createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP");
  1970. }
  1971. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  1972. $runtime_string = encode_base64($runtime_string,"");
  1973. if ($IsTimeSet || $IsAggrSet) {
  1974. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  1975. } else {
  1976. eval{$sth->execute();};
  1977. }
  1978. if ($@) {
  1979. $err = encode_base64($@,"");
  1980. Log3 ($name, 2, "DbRep $name - $@");
  1981. $dbh->disconnect;
  1982. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_DoParse finished");
  1983. return "$name|''|$device|$reading|''|$err";
  1984. }
  1985. my @array= map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  1986. if(!@array) {
  1987. if(AttrVal($name, "aggregation", "") eq "hour") {
  1988. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  1989. @array = ($runtime_string." "."0"." ".$rsf[0]."_".$rsf[1]."\n");
  1990. } else {
  1991. my @rsf = split(" ",$runtime_string_first);
  1992. @array = ($runtime_string." "."0"." ".$rsf[0]."\n");
  1993. }
  1994. }
  1995. push(@row_array, @array);
  1996. }
  1997. $sth->finish;
  1998. $dbh->disconnect;
  1999. # SQL-Laufzeit ermitteln
  2000. my $rt = tv_interval($st);
  2001. Log3 ($name, 5, "DbRep $name -> raw data of row_array result:\n @row_array");
  2002. #---------- Berechnung Ergebnishash minValue ------------------------
  2003. my $i = 1;
  2004. my %rh = ();
  2005. my $lastruntimestring;
  2006. my $row_min_time;
  2007. my ($min_value,$value);
  2008. foreach my $row (@row_array) {
  2009. my @a = split("[ \t][ \t]*", $row);
  2010. my $runtime_string = decode_base64($a[0]);
  2011. $lastruntimestring = $runtime_string if ($i == 1);
  2012. $value = $a[1];
  2013. $min_value = $a[1] if ($i == 1);
  2014. $a[-1] =~ s/:/-/g if($a[-1]); # substituieren unsupported characters -> siehe fhem.pl
  2015. my $timestamp = ($a[-1]&&$a[-2])?$a[-2]."_".$a[-1]:$a[-1];
  2016. # Leerzeichen am Ende $timestamp entfernen
  2017. $timestamp =~ s/\s+$//g;
  2018. # Test auf $value = "numeric"
  2019. if (!looks_like_number($value)) {
  2020. # $a[-1] =~ s/\s+$//g;
  2021. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in minValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  2022. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  2023. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_DoParse finished");
  2024. return "$name|''|$device|$reading|''|$err";
  2025. }
  2026. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, TIMESTAMP: $timestamp, VALUE: $value");
  2027. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$timestamp if ($i == 1); # minValue des ersten SQL-Statements in hash einfügen
  2028. if ($runtime_string eq $lastruntimestring) {
  2029. if ($value < $min_value) {
  2030. $min_value = $value;
  2031. $row_min_time = $timestamp;
  2032. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$row_min_time;
  2033. }
  2034. } else {
  2035. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen
  2036. $lastruntimestring = $runtime_string;
  2037. $min_value = $value;
  2038. $row_min_time = $timestamp;
  2039. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$row_min_time;
  2040. }
  2041. $i++;
  2042. }
  2043. #---------------------------------------------------------------------------------------------
  2044. Log3 ($name, 5, "DbRep $name - result of minValue calculation before encoding:");
  2045. foreach my $key (sort(keys(%rh))) {
  2046. Log3 ($name, 5, "runtimestring Key: $key, value: ".$rh{$key});
  2047. }
  2048. # Ergebnishash als Einzeiler zurückgeben
  2049. my $rows = join('§', %rh);
  2050. my $rowlist = encode_base64($rows,"");
  2051. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_DoParse finished");
  2052. # Background-Laufzeit ermitteln
  2053. my $brt = tv_interval($bst);
  2054. $rt = $rt.",".$brt;
  2055. return "$name|$rowlist|$device|$reading|$rt|0";
  2056. }
  2057. ####################################################################################################
  2058. # Auswertungsroutine der nichtblockierenden DB-Abfrage minValue
  2059. ####################################################################################################
  2060. sub minval_ParseDone($) {
  2061. my ($string) = @_;
  2062. my @a = split("\\|",$string);
  2063. my $hash = $defs{$a[0]};
  2064. my $name = $hash->{NAME};
  2065. my $rowlist = decode_base64($a[1]);
  2066. my $device = $a[2];
  2067. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2068. my $reading = $a[3];
  2069. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2070. my $bt = $a[4];
  2071. my ($rt,$brt) = split(",", $bt);
  2072. my $err = $a[5]?decode_base64($a[5]):undef;
  2073. my $reading_runtime_string;
  2074. Log3 ($name, 4, "DbRep $name -> Start BlockingCall minval_ParseDone");
  2075. if ($err) {
  2076. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2077. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2078. delete($hash->{HELPER}{RUNNING_PID});
  2079. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_ParseDone finished");
  2080. return;
  2081. }
  2082. my %rh = split("§", $rowlist);
  2083. Log3 ($name, 5, "DbRep $name - result of minValue calculation after decoding:");
  2084. foreach my $key (sort(keys(%rh))) {
  2085. Log3 ($name, 5, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  2086. }
  2087. # Readingaufbereitung
  2088. readingsBeginUpdate($hash);
  2089. # only for this block because of warnings if details of readings are not set
  2090. no warnings 'uninitialized';
  2091. foreach my $key (sort(keys(%rh))) {
  2092. my @k = split("\\|",$rh{$key});
  2093. my $rsf = $k[2]."__" if($k[2]);
  2094. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2095. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  2096. } else {
  2097. my $ds = $device."__" if ($device);
  2098. my $rds = $reading."__" if ($reading);
  2099. $reading_runtime_string = $rsf.$ds.$rds."MIN__".$k[0];
  2100. }
  2101. my $rv = $k[1];
  2102. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, defined($rv)?sprintf("%.4f",$rv):"-");
  2103. }
  2104. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2105. readingsEndUpdate($hash, 1);
  2106. delete($hash->{HELPER}{RUNNING_PID});
  2107. Log3 ($name, 4, "DbRep $name -> BlockingCall minval_ParseDone finished");
  2108. return;
  2109. }
  2110. ####################################################################################################
  2111. # nichtblockierende DB-Abfrage diffValue
  2112. ####################################################################################################
  2113. sub diffval_DoParse($) {
  2114. my ($string) = @_;
  2115. my ($name, $device, $reading, $ts) = split("\\§", $string);
  2116. my $hash = $defs{$name};
  2117. my $dbloghash = $hash->{dbloghash};
  2118. my $dbconn = $dbloghash->{dbconn};
  2119. my $dbuser = $dbloghash->{dbuser};
  2120. my $dblogname = $dbloghash->{NAME};
  2121. my $dbmodel = $dbloghash->{MODEL};
  2122. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2123. my ($dbh,$sql,$sth,$err,$selspec);
  2124. # Background-Startzeit
  2125. my $bst = [gettimeofday];
  2126. Log3 ($name, 4, "DbRep $name -> Start BlockingCall diffval_DoParse");
  2127. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2128. if ($@) {
  2129. $err = encode_base64($@,"");
  2130. Log3 ($name, 2, "DbRep $name - $@");
  2131. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_DoParse finished");
  2132. return "$name|''|$device|$reading|''|''|''|$err";
  2133. }
  2134. # only for this block because of warnings if details of readings are not set
  2135. no warnings 'uninitialized';
  2136. # Timestampstring to Array
  2137. my @ts = split("\\|", $ts);
  2138. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2139. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2140. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  2141. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2142. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  2143. if($dbmodel eq "MYSQL") {
  2144. $selspec = "TIMESTAMP,VALUE, if(VALUE-\@V < 0 OR \@RB = 1 , \@diff:= 0, \@diff:= VALUE-\@V ) as DIFF, \@V:= VALUE as VALUEBEFORE, \@RB:= '0' as RBIT ";
  2145. } else {
  2146. $selspec = "TIMESTAMP,VALUE";
  2147. }
  2148. # SQL-Startzeit
  2149. my $st = [gettimeofday];
  2150. # SQL zusammenstellen für DB-Operation
  2151. if ($IsTimeSet || $IsAggrSet) {
  2152. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"?","?",'');
  2153. } else {
  2154. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,'');
  2155. }
  2156. $sth = $dbh->prepare($sql);
  2157. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  2158. my @row_array;
  2159. my @array;
  2160. foreach my $row (@ts) {
  2161. my @a = split("#", $row);
  2162. my $runtime_string = $a[0];
  2163. my $runtime_string_first = $a[1];
  2164. my $runtime_string_next = $a[2];
  2165. $runtime_string = encode_base64($runtime_string,"");
  2166. # SQL zusammenstellen für Logausgabe
  2167. if ($IsTimeSet || $IsAggrSet) {
  2168. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  2169. }
  2170. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2171. if($dbmodel eq "MYSQL") {
  2172. eval {$dbh->do("set \@V:= 0, \@diff:= 0, \@diffTotal:= 0, \@RB:= 1;");}; # @\RB = Resetbit wenn neues Selektionsintervall beginnt
  2173. }
  2174. if ($@) {
  2175. $err = encode_base64($@,"");
  2176. Log3 ($name, 2, "DbRep $name - $@");
  2177. $dbh->disconnect;
  2178. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_DoParse finished");
  2179. return "$name|''|$device|$reading|''|''|''|$err";
  2180. }
  2181. if ($IsTimeSet || $IsAggrSet) {
  2182. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  2183. } else {
  2184. eval{$sth->execute();};
  2185. }
  2186. if ($@) {
  2187. $err = encode_base64($@,"");
  2188. Log3 ($name, 2, "DbRep $name - $@");
  2189. $dbh->disconnect;
  2190. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_DoParse finished");
  2191. return "$name|''|$device|$reading|''|''|''|$err";
  2192. } else {
  2193. if($dbmodel eq "MYSQL") {
  2194. @array = map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]." ".$_ -> [2]."\n" } @{ $sth->fetchall_arrayref() };
  2195. } else {
  2196. @array = map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  2197. if (@array) {
  2198. my @sp;
  2199. my $dse = 0;
  2200. my $vold;
  2201. my @sqlite_array;
  2202. foreach my $row (@array) {
  2203. @sp = split("[ \t][ \t]*", $row, 4);
  2204. my $runtime_string = $sp[0];
  2205. my $timestamp = $sp[2]?$sp[1]." ".$sp[2]:$sp[1];
  2206. my $vnew = $sp[3];
  2207. $vnew =~ tr/\n//d;
  2208. $dse = ($vold && (($vnew-$vold) > 0))?($vnew-$vold):0;
  2209. @sp = $runtime_string." ".$timestamp." ".$vnew." ".$dse."\n";
  2210. $vold = $vnew;
  2211. push(@sqlite_array, @sp);
  2212. }
  2213. @array = @sqlite_array;
  2214. }
  2215. }
  2216. if(!@array) {
  2217. if(AttrVal($name, "aggregation", "") eq "hour") {
  2218. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2219. @array = ($runtime_string." ".$rsf[0]."_".$rsf[1]."\n");
  2220. } else {
  2221. my @rsf = split(" ",$runtime_string_first);
  2222. @array = ($runtime_string." ".$rsf[0]."\n");
  2223. }
  2224. }
  2225. push(@row_array, @array);
  2226. }
  2227. }
  2228. # SQL-Laufzeit ermitteln
  2229. my $rt = tv_interval($st);
  2230. $dbh->disconnect;
  2231. Log3 ($name, 5, "DbRep $name - raw data of row_array result:\n @row_array");
  2232. my $difflimit = AttrVal($name, "diffAccept", "20"); # legt fest, bis zu welchem Wert Differenzen akzeptiert werden (Ausreißer eliminieren)
  2233. # Berechnung diffValue aus Selektionshash
  2234. my %rh = (); # Ergebnishash, wird alle Ergebniszeilen enthalten
  2235. my %ch = (); # counthash, enthält die Anzahl der verarbeiteten Datasets pro runtime_string
  2236. my $lastruntimestring;
  2237. my $i = 1;
  2238. my $lval; # immer der letzte Wert von $value
  2239. my $rslval; # runtimestring von lval
  2240. my $uediff; # Übertragsdifferenz (Differenz zwischen letzten Wert einer Aggregationsperiode und dem ersten Wert der Folgeperiode)
  2241. my $diff_current; # Differenzwert des aktuellen Datasets
  2242. my $diff_before; # Differenzwert vorheriger Datensatz
  2243. my $rejectstr; # String der ignorierten Differenzsätze
  2244. my $diff_total; # Summenwert aller berücksichtigten Teildifferenzen
  2245. my $max = ($#row_array)+1; # Anzahl aller Listenelemente
  2246. Log3 ($name, 5, "DbRep $name - data of row_array result assigned to fields:\n");
  2247. foreach my $row (@row_array) {
  2248. my @a = split("[ \t][ \t]*", $row, 6);
  2249. my $runtime_string = decode_base64($a[0]);
  2250. $lastruntimestring = $runtime_string if ($i == 1);
  2251. my $timestamp = $a[2]?$a[1]."_".$a[2]:$a[1];
  2252. my $value = $a[3]?$a[3]:0;
  2253. my $diff = $a[4]?sprintf("%.4f",$a[4]):0;
  2254. # if ($uediff) {
  2255. # $diff = $diff + $uediff;
  2256. # Log3 ($name, 4, "DbRep $name - balance difference of $uediff between $rslval and $runtime_string");
  2257. # $uediff = 0;
  2258. # }
  2259. # Leerzeichen am Ende $timestamp entfernen
  2260. $timestamp =~ s/\s+$//g;
  2261. # Test auf $value = "numeric"
  2262. if (!looks_like_number($value)) {
  2263. $a[3] =~ s/\s+$//g;
  2264. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in diffValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  2265. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  2266. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_DoParse finished");
  2267. return "$name|''|$device|$reading|''|''|''|$err";
  2268. }
  2269. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, \nTIMESTAMP: $timestamp, VALUE: $value, DIFF: $diff");
  2270. # String ignorierter Zeilen erzeugen
  2271. $diff_current = $timestamp." ".$diff;
  2272. if($diff > $difflimit) {
  2273. $rejectstr .= $diff_before." -> ".$diff_current."\n";
  2274. }
  2275. $diff_before = $diff_current;
  2276. # Ergebnishash erzeugen
  2277. if ($runtime_string eq $lastruntimestring) {
  2278. if ($i == 1) {
  2279. $diff_total = $diff?$diff:0 if($diff <= $difflimit);
  2280. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2281. $ch{$runtime_string} = 1 if($value);
  2282. $lval = $value;
  2283. $rslval = $runtime_string;
  2284. }
  2285. if ($diff) {
  2286. if($diff <= $difflimit) {
  2287. $diff_total = $diff_total+$diff;
  2288. }
  2289. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2290. $ch{$runtime_string}++ if($value && $i > 1);
  2291. $lval = $value;
  2292. $rslval = $runtime_string;
  2293. }
  2294. } else {
  2295. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen und Übertragsdifferenz bilden
  2296. $lastruntimestring = $runtime_string;
  2297. $i = 1;
  2298. $uediff = $value - $lval if($value > $lval);
  2299. $diff = $uediff;
  2300. $lval = $value if($value); # Übetrag über Perioden mit value = 0 hinweg !
  2301. $rslval = $runtime_string;
  2302. Log3 ($name, 4, "DbRep $name - balance difference of $uediff between $rslval and $runtime_string");
  2303. $diff_total = $diff?$diff:0 if($diff <= $difflimit);
  2304. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2305. $ch{$runtime_string} = 1 if($value);
  2306. $uediff = 0;
  2307. }
  2308. $i++;
  2309. }
  2310. Log3 ($name, 4, "DbRep $name - result of diffValue calculation before encoding:");
  2311. foreach my $key (sort(keys(%rh))) {
  2312. Log3 ($name, 4, "runtimestring Key: $key, value: ".$rh{$key});
  2313. }
  2314. my $ncp = calcount($hash,\%ch);
  2315. my ($ncps,$ncpslist);
  2316. if(%$ncp) {
  2317. Log3 ($name, 3, "DbRep $name - time/aggregation periods containing only one dataset -> no diffValue calc was possible in period:");
  2318. foreach my $key (sort(keys%{$ncp})) {
  2319. Log3 ($name, 3, $key) ;
  2320. }
  2321. $ncps = join('§', %$ncp);
  2322. $ncpslist = encode_base64($ncps,"");
  2323. }
  2324. # Ergebnishash als Einzeiler zurückgeben
  2325. # ignorierte Zeilen ($diff > $difflimit)
  2326. my $rowsrej = encode_base64($rejectstr,"") if($rejectstr);
  2327. # Ergebnishash
  2328. my $rows = join('§', %rh);
  2329. my $rowlist = encode_base64($rows,"");
  2330. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_DoParse finished");
  2331. # Background-Laufzeit ermitteln
  2332. my $brt = tv_interval($bst);
  2333. $rt = $rt.",".$brt;
  2334. return "$name|$rowlist|$device|$reading|$rt|$rowsrej|$ncpslist|0";
  2335. }
  2336. ####################################################################################################
  2337. # Auswertungsroutine der nichtblockierenden DB-Abfrage diffValue
  2338. ####################################################################################################
  2339. sub diffval_ParseDone($) {
  2340. my ($string) = @_;
  2341. my @a = split("\\|",$string);
  2342. my $hash = $defs{$a[0]};
  2343. my $name = $hash->{NAME};
  2344. my $rowlist = decode_base64($a[1]);
  2345. my $device = $a[2];
  2346. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2347. my $reading = $a[3];
  2348. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2349. my $bt = $a[4];
  2350. my ($rt,$brt) = split(",", $bt);
  2351. my $rowsrej = $a[5]?decode_base64($a[5]):undef; # String von Datensätzen die nicht berücksichtigt wurden (diff Schwellenwert Überschreitung)
  2352. my $ncpslist = decode_base64($a[6]); # Hash von Perioden die nicht kalkuliert werden konnten "no calc in period"
  2353. my $err = $a[7]?decode_base64($a[7]):undef;
  2354. my $reading_runtime_string;
  2355. my $difflimit = AttrVal($name, "diffAccept", "20"); # legt fest, bis zu welchem Wert Differenzen akzeptoert werden (Ausreißer eliminieren)AttrVal($name, "diffAccept", "20");
  2356. Log3 ($name, 4, "DbRep $name -> Start BlockingCall diffval_ParseDone");
  2357. if ($err) {
  2358. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2359. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2360. delete($hash->{HELPER}{RUNNING_PID});
  2361. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_ParseDone finished");
  2362. return;
  2363. }
  2364. # only for this block because of warnings if details of readings are not set
  2365. no warnings 'uninitialized';
  2366. # Auswertung hashes für state-Warning
  2367. $rowsrej =~ s/_/ /g;
  2368. Log3 ($name, 3, "DbRep $name -> data ignored while calc diffValue due to threshold overrun (diffAccept = $difflimit): \n$rowsrej")
  2369. if($rowsrej);
  2370. $rowsrej =~ s/\n/ \|\| /g;
  2371. my %ncp = split("§", $ncpslist);
  2372. my $ncpstr;
  2373. if(%ncp) {
  2374. foreach my $ncpkey (sort(keys(%ncp))) {
  2375. $ncpstr .= $ncpkey." || ";
  2376. }
  2377. }
  2378. # Readingaufbereitung
  2379. my %rh = split("§", $rowlist);
  2380. Log3 ($name, 4, "DbRep $name - result of diffValue calculation after decoding:");
  2381. foreach my $key (sort(keys(%rh))) {
  2382. Log3 ($name, 4, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  2383. }
  2384. readingsBeginUpdate($hash);
  2385. foreach my $key (sort(keys(%rh))) {
  2386. my @k = split("\\|",$rh{$key});
  2387. my $rts = $k[2]."__";
  2388. $rts =~ s/:/-/g; # substituieren unsupported characters -> siehe fhem.pl
  2389. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2390. $reading_runtime_string = $rts.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  2391. } else {
  2392. my $ds = $device."__" if ($device);
  2393. my $rds = $reading."__" if ($reading);
  2394. $reading_runtime_string = $rts.$ds.$rds."DIFF__".$k[0];
  2395. }
  2396. my $rv = $k[1];
  2397. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $rv?sprintf("%.4f",$rv):"-");
  2398. }
  2399. ReadingsBulkUpdateValue ($hash, "diff_overrun_limit_".$difflimit, $rowsrej) if($rowsrej);
  2400. ReadingsBulkUpdateValue ($hash, "less_data_in_period", $ncpstr) if($ncpstr);
  2401. ReadingsBulkUpdateTimeState($hash,$brt,$rt,($ncpstr||$rowsrej)?"Warning":"done");
  2402. readingsEndUpdate($hash, 1);
  2403. delete($hash->{HELPER}{RUNNING_PID});
  2404. Log3 ($name, 4, "DbRep $name -> BlockingCall diffval_ParseDone finished");
  2405. return;
  2406. }
  2407. ####################################################################################################
  2408. # nichtblockierende DB-Abfrage sumValue
  2409. ####################################################################################################
  2410. sub sumval_DoParse($) {
  2411. my ($string) = @_;
  2412. my ($name, $device, $reading, $ts) = split("\\§", $string);
  2413. my $hash = $defs{$name};
  2414. my $dbloghash = $hash->{dbloghash};
  2415. my $dbconn = $dbloghash->{dbconn};
  2416. my $dbuser = $dbloghash->{dbuser};
  2417. my $dblogname = $dbloghash->{NAME};
  2418. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2419. my ($dbh,$sql,$sth,$err,$selspec);
  2420. # Background-Startzeit
  2421. my $bst = [gettimeofday];
  2422. Log3 ($name, 4, "DbRep $name -> Start BlockingCall sumval_DoParse");
  2423. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2424. if ($@) {
  2425. $err = encode_base64($@,"");
  2426. Log3 ($name, 2, "DbRep $name - $@");
  2427. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
  2428. return "$name|''|$device|$reading|''|$err";
  2429. }
  2430. # only for this block because of warnings if details of readings are not set
  2431. no warnings 'uninitialized';
  2432. # Timestampstring to Array
  2433. my @ts = split("\\|", $ts);
  2434. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2435. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2436. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  2437. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2438. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  2439. if ($dbloghash->{MODEL} eq "POSTGRESQL") {
  2440. $selspec = "SUM(VALUE::numeric)";
  2441. } elsif ($dbloghash->{MODEL} eq "MYSQL") {
  2442. $selspec = "SUM(VALUE)";
  2443. } elsif ($dbloghash->{MODEL} eq "SQLITE") {
  2444. $selspec = "SUM(VALUE)";
  2445. } else {
  2446. $selspec = "SUM(VALUE)";
  2447. }
  2448. # SQL zusammenstellen für DB-Abfrage
  2449. if ($IsTimeSet || $IsAggrSet) {
  2450. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"?","?",'');
  2451. } else {
  2452. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,'');
  2453. }
  2454. eval{$sth = $dbh->prepare($sql);};
  2455. if ($@) {
  2456. $err = encode_base64($@,"");
  2457. Log3 ($name, 2, "DbRep $name - $@");
  2458. $dbh->disconnect;
  2459. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
  2460. return "$name|''|$device|$reading|''|$err";
  2461. }
  2462. # SQL-Startzeit
  2463. my $st = [gettimeofday];
  2464. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  2465. my $arrstr;
  2466. foreach my $row (@ts) {
  2467. my @a = split("#", $row);
  2468. my $runtime_string = $a[0];
  2469. my $runtime_string_first = $a[1];
  2470. my $runtime_string_next = $a[2];
  2471. # SQL zusammenstellen für Logging
  2472. if ($IsTimeSet || $IsAggrSet) {
  2473. $sql = createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  2474. }
  2475. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2476. if ($IsTimeSet || $IsAggrSet) {
  2477. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  2478. } else {
  2479. eval{$sth->execute();};
  2480. }
  2481. if ($@) {
  2482. $err = encode_base64($@,"");
  2483. Log3 ($name, 2, "DbRep $name - $@");
  2484. $dbh->disconnect;
  2485. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
  2486. return "$name|''|$device|$reading|''|$err";
  2487. }
  2488. # DB-Abfrage -> Ergebnis in @arr aufnehmen
  2489. my @line = $sth->fetchrow_array();
  2490. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  2491. if(AttrVal($name, "aggregation", "") eq "hour") {
  2492. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2493. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  2494. } else {
  2495. my @rsf = split(" ",$runtime_string_first);
  2496. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  2497. }
  2498. }
  2499. $sth->finish;
  2500. $dbh->disconnect;
  2501. # SQL-Laufzeit ermitteln
  2502. my $rt = tv_interval($st);
  2503. # Daten müssen als Einzeiler zurückgegeben werden
  2504. $arrstr = encode_base64($arrstr,"");
  2505. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_DoParse finished");
  2506. # Background-Laufzeit ermitteln
  2507. my $brt = tv_interval($bst);
  2508. $rt = $rt.",".$brt;
  2509. return "$name|$arrstr|$device|$reading|$rt|0";
  2510. }
  2511. ####################################################################################################
  2512. # Auswertungsroutine der nichtblockierenden DB-Abfrage sumValue
  2513. ####################################################################################################
  2514. sub sumval_ParseDone($) {
  2515. my ($string) = @_;
  2516. my @a = split("\\|",$string);
  2517. my $hash = $defs{$a[0]};
  2518. my $name = $hash->{NAME};
  2519. my $arrstr = decode_base64($a[1]);
  2520. my $device = $a[2];
  2521. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2522. my $reading = $a[3];
  2523. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2524. my $bt = $a[4];
  2525. my ($rt,$brt) = split(",", $bt);
  2526. my $err = $a[5]?decode_base64($a[5]):undef;
  2527. my $reading_runtime_string;
  2528. Log3 ($name, 4, "DbRep $name -> Start BlockingCall sumval_ParseDone");
  2529. if ($err) {
  2530. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2531. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2532. delete($hash->{HELPER}{RUNNING_PID});
  2533. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_ParseDone finished");
  2534. return;
  2535. }
  2536. # only for this block because of warnings if details of readings are not set
  2537. no warnings 'uninitialized';
  2538. # Readingaufbereitung
  2539. readingsBeginUpdate($hash);
  2540. my @arr = split("\\|", $arrstr);
  2541. foreach my $row (@arr) {
  2542. my @a = split("#", $row);
  2543. my $runtime_string = $a[0];
  2544. my $c = $a[1];
  2545. my $rsf = $a[2]."__";
  2546. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2547. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  2548. } else {
  2549. my $ds = $device."__" if ($device);
  2550. my $rds = $reading."__" if ($reading);
  2551. $reading_runtime_string = $rsf.$ds.$rds."SUM__".$runtime_string;
  2552. }
  2553. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-");
  2554. }
  2555. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2556. readingsEndUpdate($hash, 1);
  2557. delete($hash->{HELPER}{RUNNING_PID});
  2558. Log3 ($name, 4, "DbRep $name -> BlockingCall sumval_ParseDone finished");
  2559. return;
  2560. }
  2561. ####################################################################################################
  2562. # nichtblockierendes DB delete
  2563. ####################################################################################################
  2564. sub del_DoParse($) {
  2565. my ($string) = @_;
  2566. my ($name,$table,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  2567. my $hash = $defs{$name};
  2568. my $dbloghash = $hash->{dbloghash};
  2569. my $dbconn = $dbloghash->{dbconn};
  2570. my $dbuser = $dbloghash->{dbuser};
  2571. my $dblogname = $dbloghash->{NAME};
  2572. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2573. my ($dbh,$sql,$sth,$err,$rows);
  2574. # Background-Startzeit
  2575. my $bst = [gettimeofday];
  2576. Log3 ($name, 4, "DbRep $name -> Start BlockingCall del_DoParse");
  2577. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
  2578. if ($@) {
  2579. $err = encode_base64($@,"");
  2580. Log3 ($name, 2, "DbRep $name - $@");
  2581. Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
  2582. return "$name|''|''|$err|''|''|''";
  2583. }
  2584. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2585. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  2586. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2587. # SQL zusammenstellen für DB-Operation
  2588. if ($IsTimeSet || $IsAggrSet) {
  2589. $sql = createDeleteSql($hash,$table,$device,$reading,$runtime_string_first,$runtime_string_next,'');
  2590. } else {
  2591. $sql = createDeleteSql($hash,$table,$device,$reading,undef,undef,'');
  2592. }
  2593. $sth = $dbh->prepare($sql);
  2594. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2595. # SQL-Startzeit
  2596. my $st = [gettimeofday];
  2597. eval {$sth->execute();};
  2598. if ($@) {
  2599. $err = encode_base64($@,"");
  2600. Log3 ($name, 2, "DbRep $name - $@");
  2601. $dbh->disconnect;
  2602. Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
  2603. return "$name|''|''|$err|''|''|''";
  2604. }
  2605. $rows = $sth->rows;
  2606. $dbh->commit() if(!$dbh->{AutoCommit});
  2607. $dbh->disconnect;
  2608. # SQL-Laufzeit ermitteln
  2609. my $rt = tv_interval($st);
  2610. Log3 ($name, 5, "DbRep $name - Number of deleted rows: $rows");
  2611. Log3 ($name, 4, "DbRep $name -> BlockingCall del_DoParse finished");
  2612. # Background-Laufzeit ermitteln
  2613. my $brt = tv_interval($bst);
  2614. $rt = $rt.",".$brt;
  2615. return "$name|$rows|$rt|0|$table|$device|$reading";
  2616. }
  2617. ####################################################################################################
  2618. # Auswertungsroutine DB delete
  2619. ####################################################################################################
  2620. sub del_ParseDone($) {
  2621. my ($string) = @_;
  2622. my @a = split("\\|",$string);
  2623. my $hash = $defs{$a[0]};
  2624. my $name = $hash->{NAME};
  2625. my $rows = $a[1];
  2626. my $bt = $a[2];
  2627. my ($rt,$brt) = split(",", $bt);
  2628. my $err = $a[3]?decode_base64($a[3]):undef;
  2629. my $table = $a[4];
  2630. my $device = $a[5];
  2631. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2632. my $reading = $a[6];
  2633. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2634. Log3 ($name, 4, "DbRep $name -> Start BlockingCall del_ParseDone");
  2635. if ($err) {
  2636. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2637. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2638. delete($hash->{HELPER}{RUNNING_PID});
  2639. Log3 ($name, 4, "DbRep $name -> BlockingCall del_ParseDone finished");
  2640. return;
  2641. }
  2642. # only for this block because of warnings if details of readings are not set
  2643. no warnings 'uninitialized';
  2644. my ($reading_runtime_string, $ds, $rds);
  2645. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2646. $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."--DELETED_ROWS--";
  2647. } else {
  2648. $ds = $device."--" if ($device && $table ne "current");
  2649. $rds = $reading."--" if ($reading && $table ne "current");
  2650. $reading_runtime_string = $ds.$rds."--DELETED_ROWS_".uc($table)."--";
  2651. }
  2652. readingsBeginUpdate($hash);
  2653. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $rows);
  2654. $rows = ($table eq "current")?$rows:$ds.$rds.$rows;
  2655. Log3 ($name, 3, "DbRep $name - Entries of $hash->{DATABASE}.$table deleted: $rows");
  2656. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2657. readingsEndUpdate($hash, 1);
  2658. delete($hash->{HELPER}{RUNNING_PID});
  2659. Log3 ($name, 4, "DbRep $name -> BlockingCall del_ParseDone finished");
  2660. return;
  2661. }
  2662. ####################################################################################################
  2663. # nichtblockierendes DB insert
  2664. ####################################################################################################
  2665. sub insert_Push($) {
  2666. my ($name) = @_;
  2667. my $hash = $defs{$name};
  2668. my $dbloghash = $hash->{dbloghash};
  2669. my $dbconn = $dbloghash->{dbconn};
  2670. my $dbuser = $dbloghash->{dbuser};
  2671. my $dblogname = $dbloghash->{NAME};
  2672. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2673. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  2674. my ($err,$sth);
  2675. # Background-Startzeit
  2676. my $bst = [gettimeofday];
  2677. Log3 ($name, 4, "DbRep $name -> Start BlockingCall insert_Push");
  2678. my $dbh;
  2679. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  2680. if ($@) {
  2681. $err = encode_base64($@,"");
  2682. Log3 ($name, 2, "DbRep $name - $@");
  2683. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Push finished");
  2684. return "$name|''|''|$err";
  2685. }
  2686. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  2687. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbh);
  2688. my $i_timestamp = $hash->{HELPER}{I_TIMESTAMP};
  2689. my $i_device = $hash->{HELPER}{I_DEVICE};
  2690. my $i_type = $hash->{HELPER}{I_TYPE};
  2691. my $i_event = $hash->{HELPER}{I_EVENT};
  2692. my $i_reading = $hash->{HELPER}{I_READING};
  2693. my $i_value = $hash->{HELPER}{I_VALUE};
  2694. my $i_unit = $hash->{HELPER}{I_UNIT} ? $hash->{HELPER}{I_UNIT} : " ";
  2695. # SQL zusammenstellen für DB-Operation
  2696. Log3 ($name, 5, "DbRep $name -> data to insert Timestamp: $i_timestamp, Device: $i_device, Type: $i_type, Event: $i_event, Reading: $i_reading, Value: $i_value, Unit: $i_unit");
  2697. # SQL-Startzeit
  2698. my $st = [gettimeofday];
  2699. # insert history mit/ohne primary key
  2700. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  2701. eval { $sth = $dbh->prepare("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  2702. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  2703. eval { $sth = $dbh->prepare("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  2704. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  2705. eval { $sth = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  2706. } else {
  2707. eval { $sth = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  2708. }
  2709. if ($@) {
  2710. $err = encode_base64($@,"");
  2711. Log3 ($name, 2, "DbRep $name - $@");
  2712. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Push finished");
  2713. $dbh->disconnect();
  2714. return "$name|''|''|$err";
  2715. }
  2716. $dbh->begin_work();
  2717. eval {$sth->execute($i_timestamp, $i_device, $i_type, $i_event, $i_reading, $i_value, $i_unit);};
  2718. my $irow;
  2719. if ($@) {
  2720. $err = encode_base64($@,"");
  2721. Log3 ($name, 2, "DbRep $name - Insert new dataset into database failed".($usepkh?" (possible PK violation) ":": ")."$@");
  2722. $dbh->rollback();
  2723. $dbh->disconnect();
  2724. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Push finished");
  2725. return "$name|''|''|$err";
  2726. } else {
  2727. $dbh->commit();
  2728. $irow = $sth->rows;
  2729. $dbh->disconnect();
  2730. }
  2731. # SQL-Laufzeit ermitteln
  2732. my $rt = tv_interval($st);
  2733. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Push finished");
  2734. # Background-Laufzeit ermitteln
  2735. my $brt = tv_interval($bst);
  2736. $rt = $rt.",".$brt;
  2737. return "$name|$irow|$rt|0";
  2738. }
  2739. ####################################################################################################
  2740. # Auswertungsroutine DB insert
  2741. ####################################################################################################
  2742. sub insert_Done($) {
  2743. my ($string) = @_;
  2744. my @a = split("\\|",$string);
  2745. my $hash = $defs{$a[0]};
  2746. my $name = $hash->{NAME};
  2747. my $irow = $a[1];
  2748. my $bt = $a[2];
  2749. my ($rt,$brt) = split(",", $bt);
  2750. my $err = $a[3]?decode_base64($a[3]):undef;
  2751. Log3 ($name, 4, "DbRep $name -> Start BlockingCall insert_Done");
  2752. my $i_timestamp = delete $hash->{HELPER}{I_TIMESTAMP};
  2753. my $i_device = delete $hash->{HELPER}{I_DEVICE};
  2754. my $i_type = delete $hash->{HELPER}{I_TYPE};
  2755. my $i_event = delete $hash->{HELPER}{I_EVENT};
  2756. my $i_reading = delete $hash->{HELPER}{I_READING};
  2757. my $i_value = delete $hash->{HELPER}{I_VALUE};
  2758. my $i_unit = delete $hash->{HELPER}{I_UNIT};
  2759. if ($err) {
  2760. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2761. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2762. delete($hash->{HELPER}{RUNNING_PID});
  2763. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Done finished");
  2764. return;
  2765. }
  2766. # only for this block because of warnings if details of readings are not set
  2767. no warnings 'uninitialized';
  2768. readingsBeginUpdate($hash);
  2769. ReadingsBulkUpdateValue ($hash, "number_lines_inserted", $irow);
  2770. ReadingsBulkUpdateValue ($hash, "data_inserted", $i_timestamp.", ".$i_device.", ".$i_type.", ".$i_event.", ".$i_reading.", ".$i_value.", ".$i_unit);
  2771. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2772. readingsEndUpdate($hash, 1);
  2773. Log3 ($name, 5, "DbRep $name - Inserted into database $hash->{DATABASE} table 'history': Timestamp: $i_timestamp, Device: $i_device, Type: $i_type, Event: $i_event, Reading: $i_reading, Value: $i_value, Unit: $i_unit");
  2774. delete($hash->{HELPER}{RUNNING_PID});
  2775. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Done finished");
  2776. return;
  2777. }
  2778. ####################################################################################################
  2779. # Current-Tabelle mit Device,Reading Kombinationen aus history auffüllen
  2780. ####################################################################################################
  2781. sub currentfillup_Push($) {
  2782. my ($string) = @_;
  2783. my ($name,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  2784. my $hash = $defs{$name};
  2785. my $dbloghash = $hash->{dbloghash};
  2786. my $dbconn = $dbloghash->{dbconn};
  2787. my $dbuser = $dbloghash->{dbuser};
  2788. my $dblogname = $dbloghash->{NAME};
  2789. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2790. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  2791. my ($err,$sth,$sql,$devs,$danz,$ranz);
  2792. # Background-Startzeit
  2793. my $bst = [gettimeofday];
  2794. Log3 ($name, 4, "DbRep $name -> Start BlockingCall currentfillup_Push");
  2795. my $dbh;
  2796. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  2797. if ($@) {
  2798. $err = encode_base64($@,"");
  2799. Log3 ($name, 2, "DbRep $name - $@");
  2800. Log3 ($name, 4, "DbRep $name -> BlockingCall currentfillup_Push finished");
  2801. return "$name|''|''|$err|''|''";
  2802. }
  2803. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  2804. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbh);
  2805. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "'$runtime_string_*'"|"?" in SQL sonst undef)
  2806. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  2807. ($devs,$danz,$reading,$ranz) = specsForSql($hash,$device,$reading);
  2808. # SQL-Startzeit
  2809. my $st = [gettimeofday];
  2810. # insert history mit/ohne primary key
  2811. # SQL zusammenstellen für DB-Operation
  2812. if ($usepkc && $dbloghash->{MODEL} eq 'MYSQL') {
  2813. $sql = "INSERT IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  2814. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  2815. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  2816. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  2817. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  2818. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  2819. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  2820. if ($IsTimeSet) {
  2821. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  2822. } else {
  2823. $sql .= "1 ";
  2824. }
  2825. $sql .= "group by device,reading ;";
  2826. } elsif ($usepkc && $dbloghash->{MODEL} eq 'SQLITE') {
  2827. $sql = "INSERT OR IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  2828. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  2829. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  2830. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  2831. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  2832. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  2833. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  2834. if ($IsTimeSet) {
  2835. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  2836. } else {
  2837. $sql .= "1 ";
  2838. }
  2839. $sql .= "group by device,reading ;";
  2840. } elsif ($usepkc && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  2841. $sql = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT '2017-01-01 00:00:00',device,reading FROM history where ";
  2842. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  2843. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  2844. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  2845. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  2846. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  2847. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  2848. if ($IsTimeSet) {
  2849. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  2850. } else {
  2851. $sql .= "true ";
  2852. }
  2853. $sql .= "group by device,reading ORDER BY 2 ASC, 3 ASC ON CONFLICT DO NOTHING; ";
  2854. } else {
  2855. if($dbloghash->{MODEL} ne 'POSTGRESQL') {
  2856. # MySQL und SQLite
  2857. $sql = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  2858. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  2859. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  2860. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  2861. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  2862. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  2863. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  2864. if ($IsTimeSet) {
  2865. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  2866. } else {
  2867. $sql .= "1 ";
  2868. }
  2869. $sql .= "group by device,reading ;";
  2870. } else {
  2871. # PostgreSQL
  2872. $sql = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT '2017-01-01 00:00:00',device,reading FROM history where ";
  2873. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  2874. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  2875. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  2876. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  2877. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  2878. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  2879. if ($IsTimeSet) {
  2880. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  2881. } else {
  2882. $sql .= "true ";
  2883. }
  2884. $sql .= "group by device,reading ORDER BY 2 ASC, 3 ASC;";
  2885. }
  2886. }
  2887. # Log SQL Statement
  2888. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2889. eval { $sth = $dbh->prepare($sql); };
  2890. if ($@) {
  2891. $err = encode_base64($@,"");
  2892. Log3 ($name, 2, "DbRep $name - $@");
  2893. Log3 ($name, 4, "DbRep $name -> BlockingCall currentfillup_Push finished");
  2894. $dbh->disconnect();
  2895. return "$name|''|''|$err|''|''";
  2896. }
  2897. $dbh->begin_work();
  2898. eval {$sth->execute();};
  2899. my $irow;
  2900. if ($@) {
  2901. $err = encode_base64($@,"");
  2902. Log3 ($name, 2, "DbRep $name - Insert new dataset into database failed".($usepkh?" (possible PK violation) ":": ")."$@");
  2903. $dbh->rollback();
  2904. $dbh->disconnect();
  2905. Log3 ($name, 4, "DbRep $name -> BlockingCall currentfillup_Push finished");
  2906. return "$name|''|''|$err|''|''";
  2907. } else {
  2908. $dbh->commit();
  2909. $irow = $sth->rows;
  2910. $dbh->disconnect();
  2911. }
  2912. # SQL-Laufzeit ermitteln
  2913. my $rt = tv_interval($st);
  2914. Log3 ($name, 4, "DbRep $name -> BlockingCall insert_Push finished");
  2915. # Background-Laufzeit ermitteln
  2916. my $brt = tv_interval($bst);
  2917. $rt = $rt.",".$brt;
  2918. return "$name|$irow|$rt|0|$device|$reading";
  2919. }
  2920. ####################################################################################################
  2921. # Auswertungsroutine Current-Tabelle auffüllen
  2922. ####################################################################################################
  2923. sub currentfillup_Done($) {
  2924. my ($string) = @_;
  2925. my @a = split("\\|",$string);
  2926. my $hash = $defs{$a[0]};
  2927. my $name = $hash->{NAME};
  2928. my $irow = $a[1];
  2929. my $bt = $a[2];
  2930. my ($rt,$brt) = split(",", $bt);
  2931. my $err = $a[3]?decode_base64($a[3]):undef;
  2932. my $device = $a[4];
  2933. my $reading = $a[5];
  2934. undef $device if ($device =~ m(^%$));
  2935. undef $reading if ($reading =~ m(^%$));
  2936. Log3 ($name, 4, "DbRep $name -> Start BlockingCall currentfillup_Done");
  2937. if ($err) {
  2938. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2939. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2940. delete($hash->{HELPER}{RUNNING_PID});
  2941. Log3 ($name, 4, "DbRep $name -> BlockingCall currentfillup_Done finished");
  2942. return;
  2943. }
  2944. # only for this block because of warnings if details of readings are not set
  2945. no warnings 'uninitialized';
  2946. my $rowstr;
  2947. $rowstr = $irow if(!$device && !$reading);
  2948. $rowstr = $irow." - limited by device: ".$device if($device && !$reading);
  2949. $rowstr = $irow." - limited by reading: ".$reading if(!$device && $reading);
  2950. $rowstr = $irow." - limited by device: ".$device." and reading: ".$reading if($device && $reading);
  2951. readingsBeginUpdate($hash);
  2952. ReadingsBulkUpdateValue($hash, "number_lines_inserted", $rowstr);
  2953. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2954. readingsEndUpdate($hash, 1);
  2955. Log3 ($name, 3, "DbRep $name - Table '$hash->{DATABASE}'.'current' filled up with rows: $rowstr");
  2956. delete($hash->{HELPER}{RUNNING_PID});
  2957. Log3 ($name, 4, "DbRep $name -> BlockingCall currentfillup_Done finished");
  2958. return;
  2959. }
  2960. ####################################################################################################
  2961. # nichtblockierendes DB deviceRename / readingRename
  2962. ####################################################################################################
  2963. sub devren_Push($) {
  2964. my ($name) = @_;
  2965. my $hash = $defs{$name};
  2966. my $dbloghash = $hash->{dbloghash};
  2967. my $dbconn = $dbloghash->{dbconn};
  2968. my $dbuser = $dbloghash->{dbuser};
  2969. my $dblogname = $dbloghash->{NAME};
  2970. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2971. my ($dbh,$err);
  2972. # Background-Startzeit
  2973. my $bst = [gettimeofday];
  2974. Log3 ($name, 4, "DbRep $name -> Start BlockingCall devren_Push");
  2975. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
  2976. if ($@) {
  2977. $err = encode_base64($@,"");
  2978. Log3 ($name, 2, "DbRep $name - $@");
  2979. Log3 ($name, 4, "DbRep $name -> BlockingCall devren_Push finished");
  2980. return "$name|''|''|$err";
  2981. }
  2982. my $renmode = $hash->{HELPER}{RENMODE};
  2983. # SQL-Startzeit
  2984. my $st = [gettimeofday];
  2985. my ($sth,$old,$new);
  2986. $dbh->begin_work();
  2987. if ($renmode eq "devren") {
  2988. $old = delete $hash->{HELPER}{OLDDEV};
  2989. $new = delete $hash->{HELPER}{NEWDEV};
  2990. # SQL zusammenstellen für DB-Operation
  2991. Log3 ($name, 5, "DbRep $name -> Rename old device name \"$old\" to new device name \"$new\" in database $dblogname ");
  2992. # prepare DB operation
  2993. $sth = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=TIMESTAMP,DEVICE=? WHERE DEVICE=? ") ;
  2994. } elsif ($renmode eq "readren") {
  2995. $old = delete $hash->{HELPER}{OLDREAD};
  2996. $new = delete $hash->{HELPER}{NEWREAD};
  2997. # SQL zusammenstellen für DB-Operation
  2998. Log3 ($name, 5, "DbRep $name -> Rename old reading name \"$old\" to new reading name \"$new\" in database $dblogname ");
  2999. # prepare DB operation
  3000. $sth = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=TIMESTAMP,READING=? WHERE READING=? ") ;
  3001. }
  3002. eval {$sth->execute($new, $old);};
  3003. my $urow;
  3004. if ($@) {
  3005. $err = encode_base64($@,"");
  3006. Log3 ($name, 2, "DbRep $name - Failed to rename old device name \"$old\" to new device name \"$new\": $@") if($renmode eq "devren");
  3007. Log3 ($name, 2, "DbRep $name - Failed to rename old reading name \"$old\" to new reading name \"$new\": $@") if($renmode eq "readren");
  3008. $dbh->rollback();
  3009. $dbh->disconnect();
  3010. Log3 ($name, 4, "DbRep $name -> BlockingCall devren_Push finished");
  3011. return "$name|''|''|$err";
  3012. } else {
  3013. $dbh->commit();
  3014. $urow = $sth->rows;
  3015. $dbh->disconnect();
  3016. }
  3017. # SQL-Laufzeit ermitteln
  3018. my $rt = tv_interval($st);
  3019. Log3 ($name, 4, "DbRep $name -> BlockingCall devren_Push finished");
  3020. # Background-Laufzeit ermitteln
  3021. my $brt = tv_interval($bst);
  3022. $rt = $rt.",".$brt;
  3023. return "$name|$urow|$rt|0|$old|$new";
  3024. }
  3025. ####################################################################################################
  3026. # Auswertungsroutine DB deviceRename
  3027. ####################################################################################################
  3028. sub devren_Done($) {
  3029. my ($string) = @_;
  3030. my @a = split("\\|",$string);
  3031. my $hash = $defs{$a[0]};
  3032. my $name = $hash->{NAME};
  3033. my $urow = $a[1];
  3034. my $bt = $a[2];
  3035. my ($rt,$brt) = split(",", $bt);
  3036. my $err = $a[3]?decode_base64($a[3]):undef;
  3037. my $old = $a[4];
  3038. my $new = $a[5];
  3039. Log3 ($name, 4, "DbRep $name -> Start BlockingCall devren_Done");
  3040. my $renmode = delete $hash->{HELPER}{RENMODE};
  3041. if ($err) {
  3042. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3043. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3044. delete($hash->{HELPER}{RUNNING_PID});
  3045. Log3 ($name, 4, "DbRep $name -> BlockingCall devren_Done finished");
  3046. return;
  3047. }
  3048. # only for this block because of warnings if details of readings are not set
  3049. no warnings 'uninitialized';
  3050. readingsBeginUpdate($hash);
  3051. ReadingsBulkUpdateValue ($hash, "number_lines_updated", $urow);
  3052. if($renmode eq "devren") {
  3053. ReadingsBulkUpdateValue ($hash, "device_renamed", "old: ".$old." to new: ".$new) if($urow != 0);
  3054. ReadingsBulkUpdateValue ($hash, "device_not_renamed", "Warning - old: ".$old." not found, not renamed to new: ".$new)
  3055. if($urow == 0);
  3056. }
  3057. if($renmode eq "readren") {
  3058. ReadingsBulkUpdateValue ($hash, "reading_renamed", "old: ".$old." to new: ".$new) if($urow != 0);
  3059. ReadingsBulkUpdateValue ($hash, "reading_not_renamed", "Warning - old: ".$old." not found, not renamed to new: ".$new)
  3060. if ($urow == 0);
  3061. }
  3062. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3063. readingsEndUpdate($hash, 1);
  3064. if ($urow != 0) {
  3065. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - DEVICE renamed in \"$hash->{DATABASE}\", old: \"$old\", new: \"$new\", amount: $urow ") if($renmode eq "devren");
  3066. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - READING renamed in \"$hash->{DATABASE}\", old: \"$old\", new: \"$new\", amount: $urow ") if($renmode eq "readren");
  3067. } else {
  3068. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - WARNING - old device \"$old\" was not found in database \"$hash->{DATABASE}\" ") if($renmode eq "devren");
  3069. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - WARNING - old reading \"$old\" was not found in database \"$hash->{DATABASE}\" ") if($renmode eq "readren");
  3070. }
  3071. delete($hash->{HELPER}{RUNNING_PID});
  3072. Log3 ($name, 4, "DbRep $name -> BlockingCall devren_Done finished");
  3073. return;
  3074. }
  3075. ####################################################################################################
  3076. # nichtblockierende DB-Abfrage fetchrows
  3077. ####################################################################################################
  3078. sub fetchrows_DoParse($) {
  3079. my ($string) = @_;
  3080. my ($name,$table,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  3081. my $hash = $defs{$name};
  3082. my $dbloghash = $hash->{dbloghash};
  3083. my $dbconn = $dbloghash->{dbconn};
  3084. my $dbuser = $dbloghash->{dbuser};
  3085. my $dblogname = $dbloghash->{NAME};
  3086. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3087. my $limit = AttrVal($name, "limit", 1000);
  3088. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3089. my ($err,$dbh,$sth,$sql,$rowlist,$nrows);
  3090. # Background-Startzeit
  3091. my $bst = [gettimeofday];
  3092. Log3 ($name, 4, "DbRep $name -> Start BlockingCall fetchrows_DoParse");
  3093. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3094. if ($@) {
  3095. $err = encode_base64($@,"");
  3096. Log3 ($name, 2, "DbRep $name - $@");
  3097. Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
  3098. return "$name|''|''|$err|''";
  3099. }
  3100. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "'$runtime_string_*'"|"?" in SQL sonst undef)
  3101. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  3102. # SQL zusammenstellen für DB-Abfrage
  3103. if ($IsTimeSet) {
  3104. $sql = createSelectSql($hash,$table,"DEVICE,READING,TIMESTAMP,VALUE,UNIT",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP DESC LIMIT ".($limit+1));
  3105. } else {
  3106. $sql = createSelectSql($hash,$table,"DEVICE,READING,TIMESTAMP,VALUE,UNIT",$device,$reading,undef,undef,"ORDER BY TIMESTAMP DESC LIMIT ".($limit+1));
  3107. }
  3108. $sth = $dbh->prepare($sql);
  3109. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3110. # SQL-Startzeit
  3111. my $st = [gettimeofday];
  3112. eval {$sth->execute();};
  3113. if ($@) {
  3114. $err = encode_base64($@,"");
  3115. Log3 ($name, 2, "DbRep $name - $@");
  3116. $dbh->disconnect;
  3117. Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
  3118. return "$name|''|''|$err|''";
  3119. }
  3120. no warnings 'uninitialized';
  3121. my @row_array = map { $_ -> [0]." ".$_ -> [1]." ".$_ -> [2]." ".$_ -> [3]." ".$_ -> [4]."\n" } @{$sth->fetchall_arrayref()};
  3122. use warnings;
  3123. $nrows = $#row_array+1; # Anzahl der Ergebniselemente
  3124. pop @row_array if($nrows>$limit); # das zuviel selektierte Element wegpoppen wenn Limit überschritten
  3125. if ($utf8) {
  3126. $rowlist = Encode::encode_utf8(join('|', @row_array));
  3127. } else {
  3128. $rowlist = join('|', @row_array);
  3129. }
  3130. Log3 ($name, 5, "DbRep $name -> row result list:\n$rowlist");
  3131. # SQL-Laufzeit ermitteln
  3132. my $rt = tv_interval($st);
  3133. $dbh->disconnect;
  3134. # Daten müssen als Einzeiler zurückgegeben werden
  3135. $rowlist = encode_base64($rowlist,"");
  3136. Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_DoParse finished");
  3137. # Background-Laufzeit ermitteln
  3138. my $brt = tv_interval($bst);
  3139. $rt = $rt.",".$brt;
  3140. return "$name|$rowlist|$rt|0|$nrows";
  3141. }
  3142. ####################################################################################################
  3143. # Auswertungsroutine der nichtblockierenden DB-Abfrage fetchrows
  3144. ####################################################################################################
  3145. sub fetchrows_ParseDone($) {
  3146. my ($string) = @_;
  3147. my @a = split("\\|",$string);
  3148. my $hash = $defs{$a[0]};
  3149. my $rowlist = decode_base64($a[1]);
  3150. my $bt = $a[2];
  3151. my ($rt,$brt) = split(",", $bt);
  3152. my $err = $a[3]?decode_base64($a[3]):undef;
  3153. my $nrows = $a[4];
  3154. my $name = $hash->{NAME};
  3155. my $reading = AttrVal($name, "reading", undef);
  3156. my $limit = AttrVal($name, "limit", 1000);
  3157. my @i;
  3158. my @row;
  3159. my $reading_runtime_string;
  3160. Log3 ($name, 4, "DbRep $name -> Start BlockingCall fetchrows_ParseDone");
  3161. if ($err) {
  3162. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3163. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3164. delete($hash->{HELPER}{RUNNING_PID});
  3165. Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_ParseDone finished");
  3166. return;
  3167. }
  3168. my @row_array = split("\\|", $rowlist);
  3169. Log3 ($name, 5, "DbRep $name - row_array decoded: @row_array");
  3170. # Readingaufbereitung
  3171. readingsBeginUpdate($hash);
  3172. foreach my $row (@row_array) {
  3173. my @a = split("[ \t][ \t]*", $row, 5);
  3174. my $dev = $a[0];
  3175. my $rea = $a[1];
  3176. $a[3] =~ s/:/-/g; # substituieren unsopported characters ":" -> siehe fhem.pl
  3177. my $ts = $a[2]."_".$a[3];
  3178. my $val = $a[4];
  3179. my $unt = $a[5];
  3180. if ($reading && AttrVal($hash->{NAME}, "readingNameMap", "")) {
  3181. $reading_runtime_string = $ts."__".AttrVal($hash->{NAME}, "readingNameMap", "") ;
  3182. } else {
  3183. $reading_runtime_string = $ts."__".$dev."__".$rea;
  3184. }
  3185. $val = $unt?$val." ".$unt:$val;
  3186. ReadingsBulkUpdateValue($hash, $reading_runtime_string, $val);
  3187. }
  3188. my $sfx = AttrVal("global", "language", "EN");
  3189. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  3190. ReadingsBulkUpdateValue($hash, "number_fetched_rows", ($nrows>$limit)?$nrows-1:$nrows);
  3191. ReadingsBulkUpdateTimeState($hash,$brt,$rt,($nrows-$limit>0)?
  3192. "<html>done - Warning: present rows exceed specified limit, adjust attribute <a href='https://fhem.de/commandref${sfx}.html#DbRepattrlimit' target='_blank'>limit</a></html>":"done");
  3193. readingsEndUpdate($hash, 1);
  3194. delete($hash->{HELPER}{RUNNING_PID});
  3195. Log3 ($name, 4, "DbRep $name -> BlockingCall fetchrows_ParseDone finished");
  3196. return;
  3197. }
  3198. ####################################################################################################
  3199. # nichtblockierende DB-Funktion expfile
  3200. ####################################################################################################
  3201. sub expfile_DoParse($) {
  3202. my ($string) = @_;
  3203. my ($name, $device, $reading, $runtime_string_first, $runtime_string_next) = split("\\|", $string);
  3204. my $hash = $defs{$name};
  3205. my $dbloghash = $hash->{dbloghash};
  3206. my $dbconn = $dbloghash->{dbconn};
  3207. my $dbuser = $dbloghash->{dbuser};
  3208. my $dblogname = $dbloghash->{NAME};
  3209. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3210. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3211. my ($dbh,$sth,$sql);
  3212. my $err=0;
  3213. # Background-Startzeit
  3214. my $bst = [gettimeofday];
  3215. Log3 ($name, 4, "DbRep $name -> Start BlockingCall expfile_DoParse");
  3216. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3217. if ($@) {
  3218. $err = encode_base64($@,"");
  3219. Log3 ($name, 2, "DbRep $name - $@");
  3220. Log3 ($name, 4, "DbRep $name -> BlockingCall expfile_DoParse finished");
  3221. return "$name|''|''|$err|''|''";
  3222. }
  3223. my $outfile = AttrVal($name, "expimpfile", undef);
  3224. if (open(FH, ">:utf8", "$outfile")) {
  3225. binmode (FH) if(!$utf8);
  3226. } else {
  3227. $err = encode_base64("could not open ".$outfile.": ".$!,"");
  3228. return "$name|''|''|$err";
  3229. }
  3230. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "'$runtime_string_*'"|"?" in SQL sonst undef)
  3231. my ($IsTimeSet,$IsAggrSet) = checktimeaggr($hash);
  3232. # SQL zusammenstellen für DB-Abfrage
  3233. if ($IsTimeSet) {
  3234. $sql = createSelectSql($hash,"history","TIMESTAMP,DEVICE,TYPE,EVENT,READING,VALUE,UNIT",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP");
  3235. } else {
  3236. $sql = createSelectSql($hash,"history","TIMESTAMP,DEVICE,TYPE,EVENT,READING,VALUE,UNIT",$device,$reading,undef,undef,"ORDER BY TIMESTAMP");
  3237. }
  3238. $sth = $dbh->prepare($sql);
  3239. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3240. # SQL-Startzeit
  3241. my $st = [gettimeofday];
  3242. eval {$sth->execute();};
  3243. my $nrows = 0;
  3244. if ($@) {
  3245. $err = encode_base64($@,"");
  3246. Log3 ($name, 2, "DbRep $name - $@");
  3247. $dbh->disconnect;
  3248. Log3 ($name, 4, "DbRep $name -> BlockingCall expfile_DoParse finished");
  3249. return "$name|''|''|$err|''|''";
  3250. } else {
  3251. # only for this block because of warnings of uninitialized values
  3252. no warnings 'uninitialized';
  3253. while (my $row = $sth->fetchrow_arrayref) {
  3254. print FH join(',', map { s{"}{""}g; "\"$_\""; } @$row), "\n";
  3255. Log3 ($name, 5, "DbRep $name -> write row: @$row");
  3256. # Anzahl der Datensätze
  3257. $nrows++;
  3258. }
  3259. close(FH);
  3260. }
  3261. # SQL-Laufzeit ermitteln
  3262. my $rt = tv_interval($st);
  3263. $sth->finish;
  3264. $dbh->disconnect;
  3265. Log3 ($name, 4, "DbRep $name -> BlockingCall expfile_DoParse finished");
  3266. # Background-Laufzeit ermitteln
  3267. my $brt = tv_interval($bst);
  3268. $rt = $rt.",".$brt;
  3269. return "$name|$nrows|$rt|$err|$device|$reading";
  3270. }
  3271. ####################################################################################################
  3272. # Auswertungsroutine der nichtblockierenden DB-Funktion expfile
  3273. ####################################################################################################
  3274. sub expfile_ParseDone($) {
  3275. my ($string) = @_;
  3276. my @a = split("\\|",$string);
  3277. my $hash = $defs{$a[0]};
  3278. my $nrows = $a[1];
  3279. my $bt = $a[2];
  3280. my ($rt,$brt) = split(",", $bt);
  3281. my $err = $a[3]?decode_base64($a[3]):undef;
  3282. my $name = $hash->{NAME};
  3283. my $device = $a[4];
  3284. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3285. my $reading = $a[5];
  3286. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3287. Log3 ($name, 4, "DbRep $name -> Start BlockingCall expfile_ParseDone");
  3288. if ($err) {
  3289. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3290. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3291. delete($hash->{HELPER}{RUNNING_PID});
  3292. Log3 ($name, 4, "DbRep $name -> BlockingCall expfile_ParseDone finished");
  3293. return;
  3294. }
  3295. # only for this block because of warnings if details of readings are not set
  3296. no warnings 'uninitialized';
  3297. my $ds = $device." -- " if ($device);
  3298. my $rds = $reading." -- " if ($reading);
  3299. my $export_string = $ds.$rds." -- ROWS EXPORTED TO FILE -- ";
  3300. readingsBeginUpdate($hash);
  3301. ReadingsBulkUpdateValue ($hash, $export_string, $nrows);
  3302. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3303. readingsEndUpdate($hash, 1);
  3304. my $rows = $ds.$rds.$nrows;
  3305. Log3 ($name, 3, "DbRep $name - Number of exported datasets from $hash->{DATABASE} to file ".AttrVal($name, "expimpfile", undef).": $rows.");
  3306. delete($hash->{HELPER}{RUNNING_PID});
  3307. Log3 ($name, 4, "DbRep $name -> BlockingCall expfile_ParseDone finished");
  3308. return;
  3309. }
  3310. ####################################################################################################
  3311. # nichtblockierende DB-Funktion impfile
  3312. ####################################################################################################
  3313. sub impfile_Push($) {
  3314. my ($name) = @_;
  3315. my $hash = $defs{$name};
  3316. my $dbloghash = $hash->{dbloghash};
  3317. my $dbconn = $dbloghash->{dbconn};
  3318. my $dbuser = $dbloghash->{dbuser};
  3319. my $dblogname = $dbloghash->{NAME};
  3320. my $dbmodel = $hash->{dbloghash}{MODEL};
  3321. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3322. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3323. my $err=0;
  3324. my $sth;
  3325. # Background-Startzeit
  3326. my $bst = [gettimeofday];
  3327. Log3 ($name, 4, "DbRep $name -> Start BlockingCall impfile_Push");
  3328. my $dbh;
  3329. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3330. if ($@) {
  3331. $err = encode_base64($@,"");
  3332. Log3 ($name, 2, "DbRep $name - $@");
  3333. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3334. return "$name|''|''|$err";
  3335. }
  3336. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  3337. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbh);
  3338. my $infile = AttrVal($name, "expimpfile", undef);
  3339. if (open(FH, "<:utf8", "$infile")) {
  3340. binmode (FH) if(!$utf8);
  3341. } else {
  3342. $err = encode_base64("could not open ".$infile.": ".$!,"");
  3343. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3344. return "$name|''|''|$err";
  3345. }
  3346. # only for this block because of warnings if details inline is not set
  3347. no warnings 'uninitialized';
  3348. # SQL-Startzeit
  3349. my $st = [gettimeofday];
  3350. my $al;
  3351. # Datei zeilenweise einlesen und verarbeiten !
  3352. # Beispiel Inline:
  3353. # "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573",""
  3354. # insert history mit/ohne primary key
  3355. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  3356. eval { $sth = $dbh->prepare_cached("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3357. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  3358. eval { $sth = $dbh->prepare_cached("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3359. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  3360. eval { $sth = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  3361. } else {
  3362. eval { $sth = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3363. }
  3364. if ($@) {
  3365. $err = encode_base64($@,"");
  3366. Log3 ($name, 2, "DbRep $name - $@");
  3367. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3368. $dbh->disconnect();
  3369. return "$name|''|''|$err";
  3370. }
  3371. $dbh->begin_work();
  3372. my $irowdone = 0;
  3373. my $irowcount = 0;
  3374. my $warn = 0;
  3375. while (<FH>) {
  3376. $al = $_;
  3377. chomp $al;
  3378. my @alarr = split("\",\"", $al);
  3379. foreach(@alarr) {
  3380. tr/"//d;
  3381. }
  3382. my $i_timestamp = $alarr[0];
  3383. # $i_timestamp =~ tr/"//d;
  3384. my $i_device = $alarr[1];
  3385. my $i_type = $alarr[2];
  3386. my $i_event = $alarr[3];
  3387. my $i_reading = $alarr[4];
  3388. my $i_value = $alarr[5];
  3389. my $i_unit = $alarr[6] ? $alarr[6]: " ";
  3390. $irowcount++;
  3391. next if(!$i_timestamp); #leerer Datensatz
  3392. # check ob TIMESTAMP Format ok ?
  3393. my ($i_date, $i_time) = split(" ",$i_timestamp);
  3394. if ($i_date !~ /(\d{4})-(\d{2})-(\d{2})/ || $i_time !~ /(\d{2}):(\d{2}):(\d{2})/) {
  3395. $err = encode_base64("Format of date/time is not valid in row $irowcount of $infile. Must be format \"YYYY-MM-DD HH:MM:SS\" !","");
  3396. Log3 ($name, 2, "DbRep $name -> ERROR - Import from file $infile was not done. Invalid date/time field format in row $irowcount.");
  3397. close(FH);
  3398. $dbh->rollback;
  3399. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3400. return "$name|''|''|$err";
  3401. }
  3402. # Daten auf maximale Länge (entsprechend der Feldlänge in DbLog DB create-scripts) beschneiden wenn nicht SQLite
  3403. if ($dbmodel ne 'SQLITE') {
  3404. $i_device = substr($i_device,0, $dbrep_col{DEVICE});
  3405. $i_reading = substr($i_reading,0, $dbrep_col{READING});
  3406. $i_value = substr($i_value,0, $dbrep_col{VALUE});
  3407. $i_unit = substr($i_unit,0, $dbrep_col{UNIT}) if($i_unit);
  3408. }
  3409. Log3 ($name, 5, "DbRep $name -> data to insert Timestamp: $i_timestamp, Device: $i_device, Type: $i_type, Event: $i_event, Reading: $i_reading, Value: $i_value, Unit: $i_unit");
  3410. if($i_timestamp && $i_device && $i_reading) {
  3411. eval {$sth->execute($i_timestamp, $i_device, $i_type, $i_event, $i_reading, $i_value, $i_unit);};
  3412. if ($@) {
  3413. $err = encode_base64($@,"");
  3414. Log3 ($name, 2, "DbRep $name - Failed to insert new dataset into database: $@");
  3415. close(FH);
  3416. $dbh->rollback;
  3417. $dbh->disconnect;
  3418. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3419. return "$name|''|''|$err";
  3420. } else {
  3421. $irowdone++
  3422. }
  3423. } else {
  3424. $err = encode_base64("format error in in row $irowcount of $infile.","");
  3425. Log3 ($name, 2, "DbRep $name -> ERROR - Import of datasets of file $infile was NOT done. Formaterror in row $irowcount !");
  3426. close(FH);
  3427. $dbh->rollback;
  3428. $dbh->disconnect;
  3429. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3430. return "$name|''|''|$err";
  3431. }
  3432. }
  3433. $dbh->commit;
  3434. $dbh->disconnect;
  3435. close(FH);
  3436. # SQL-Laufzeit ermitteln
  3437. my $rt = tv_interval($st);
  3438. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_Push finished");
  3439. # Background-Laufzeit ermitteln
  3440. my $brt = tv_interval($bst);
  3441. $rt = $rt.",".$brt;
  3442. return "$name|$irowdone|$rt|$err";
  3443. }
  3444. ####################################################################################################
  3445. # Auswertungsroutine der nichtblockierenden DB-Funktion impfile
  3446. ####################################################################################################
  3447. sub impfile_PushDone($) {
  3448. my ($string) = @_;
  3449. my @a = split("\\|",$string);
  3450. my $hash = $defs{$a[0]};
  3451. my $irowdone = $a[1];
  3452. my $bt = $a[2];
  3453. my ($rt,$brt) = split(",", $bt);
  3454. my $err = $a[3]?decode_base64($a[3]):undef;
  3455. my $name = $hash->{NAME};
  3456. Log3 ($name, 4, "DbRep $name -> Start BlockingCall impfile_PushDone");
  3457. if ($err) {
  3458. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3459. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3460. delete($hash->{HELPER}{RUNNING_PID});
  3461. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_PushDone finished");
  3462. return;
  3463. }
  3464. # only for this block because of warnings if details of readings are not set
  3465. no warnings 'uninitialized';
  3466. my $import_string = " -- ROWS IMPORTED FROM FILE -- ";
  3467. readingsBeginUpdate($hash);
  3468. ReadingsBulkUpdateValue ($hash, $import_string, $irowdone);
  3469. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3470. readingsEndUpdate($hash, 1);
  3471. Log3 ($name, 3, "DbRep $name - Number of imported datasets to $hash->{DATABASE} from file ".AttrVal($name, "expimpfile", undef).": $irowdone");
  3472. delete($hash->{HELPER}{RUNNING_PID});
  3473. Log3 ($name, 4, "DbRep $name -> BlockingCall impfile_PushDone finished");
  3474. return;
  3475. }
  3476. ####################################################################################################
  3477. # nichtblockierende DB-Abfrage sqlCmd - generischer SQL-Befehl - name | opt | sqlcommand
  3478. ####################################################################################################
  3479. # set logdbrep sqlCmd select count(*) from history
  3480. # set logdbrep sqlCmd select DEVICE,count(*) from history group by DEVICE HAVING count(*) > 10000
  3481. sub sqlCmd_DoParse($) {
  3482. my ($string) = @_;
  3483. my ($name, $opt, $runtime_string_first, $runtime_string_next, $cmd) = split("\\|", $string);
  3484. my $hash = $defs{$name};
  3485. my $dbloghash = $hash->{dbloghash};
  3486. my $dbconn = $dbloghash->{dbconn};
  3487. my $dbuser = $dbloghash->{dbuser};
  3488. my $dblogname = $dbloghash->{NAME};
  3489. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3490. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3491. my $err;
  3492. # Background-Startzeit
  3493. my $bst = [gettimeofday];
  3494. Log3 ($name, 4, "DbRep $name -> Start BlockingCall sqlCmd_DoParse");
  3495. my $dbh;
  3496. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3497. if ($@) {
  3498. $err = encode_base64($@,"");
  3499. Log3 ($name, 2, "DbRep $name - $@");
  3500. Log3 ($name, 4, "DbRep $name -> BlockingCall sqlCmd_DoParse finished");
  3501. return "$name|''|$opt|$cmd|''|''|$err";
  3502. }
  3503. # only for this block because of warnings if details of readings are not set
  3504. no warnings 'uninitialized';
  3505. my $sql = ($cmd =~ m/\;$/)?$cmd:$cmd.";";
  3506. # Allow inplace replacement of keywords for timings (use time attribute syntax)
  3507. $sql =~ s/§timestamp_begin§/'$runtime_string_first'/g;
  3508. $sql =~ s/§timestamp_end§/'$runtime_string_next'/g;
  3509. # Debug "SQL :".$sql.":";
  3510. Log3($name, 4, "DbRep $name - SQL execute: $sql");
  3511. # SQL-Startzeit
  3512. my $st = [gettimeofday];
  3513. my ($sth,$r);
  3514. eval {$sth = $dbh->prepare($sql);
  3515. $r = $sth->execute();
  3516. };
  3517. if ($@) {
  3518. # error bei sql-execute
  3519. $err = encode_base64($@,"");
  3520. Log3 ($name, 2, "DbRep $name - ERROR - $@");
  3521. $dbh->disconnect;
  3522. Log3 ($name, 4, "DbRep $name -> BlockingCall sqlCmd_DoParse finished");
  3523. return "$name|''|$opt|$sql|''|''|$err";
  3524. }
  3525. my @rows;
  3526. my $nrows = 0;
  3527. if($sql =~ m/^\s*(select|pragma|show)/is) {
  3528. while (my @line = $sth->fetchrow_array()) {
  3529. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  3530. my $row = join("|", @line);
  3531. # im Ergebnis immer § ersetzen (wegen join Delimiter "§")
  3532. $row =~ s/§/|°escaped°|/g;
  3533. push(@rows, $row);
  3534. # Anzahl der Datensätze
  3535. $nrows++;
  3536. }
  3537. } else {
  3538. $nrows = $sth->rows;
  3539. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  3540. if ($@) {
  3541. $err = encode_base64($@,"");
  3542. Log3 ($name, 2, "DbRep $name - ERROR - $@");
  3543. $dbh->disconnect;
  3544. Log3 ($name, 4, "DbRep $name -> BlockingCall sqlCmd_DoParse finished");
  3545. return "$name|''|$opt|$sql|''|''|$err";
  3546. }
  3547. push(@rows, $r);
  3548. my $com = (split(" ",$sql, 2))[0];
  3549. Log3 ($name, 3, "DbRep $name - Number of entries processed in db $hash->{DATABASE}: $nrows by $com");
  3550. }
  3551. $sth->finish;
  3552. # SQL-Laufzeit ermitteln
  3553. my $rt = tv_interval($st);
  3554. $dbh->disconnect;
  3555. # Daten müssen als Einzeiler zurückgegeben werden
  3556. my $rowstring = join("§", @rows);
  3557. $rowstring = encode_base64($rowstring,"");
  3558. Log3 ($name, 4, "DbRep $name -> BlockingCall count_DoParse finished");
  3559. # Background-Laufzeit ermitteln
  3560. my $brt = tv_interval($bst);
  3561. $rt = $rt.",".$brt;
  3562. return "$name|$rowstring|$opt|$sql|$nrows|$rt|$err";
  3563. }
  3564. ####################################################################################################
  3565. # Auswertungsroutine der nichtblockierenden DB-Abfrage sqlCmd
  3566. ####################################################################################################
  3567. sub sqlCmd_ParseDone($) {
  3568. my ($string) = @_;
  3569. my @a = split("\\|",$string);
  3570. my $hash = $defs{$a[0]};
  3571. my $name = $hash->{NAME};
  3572. my $rowstring = decode_base64($a[1]);
  3573. my $opt = $a[2];
  3574. my $cmd = $a[3];
  3575. my $nrows = $a[4];
  3576. my $bt = $a[5];
  3577. my ($rt,$brt) = split(",", $bt);
  3578. my $err = $a[6]?decode_base64($a[6]):undef;
  3579. my $srf = AttrVal($name, "sqlResultFormat", "separated");
  3580. Log3 ($name, 4, "DbRep $name -> Start BlockingCall sqlCmd_ParseDone");
  3581. if ($err) {
  3582. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3583. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3584. delete($hash->{HELPER}{RUNNING_PID});
  3585. Log3 ($name, 4, "DbRep $name -> BlockingCall sqlCmd_ParseDone finished");
  3586. return;
  3587. }
  3588. Log3 ($name, 5, "DbRep $name - SQL result decoded: $rowstring") if($rowstring);
  3589. # only for this block because of warnings if details of readings are not set
  3590. no warnings 'uninitialized';
  3591. # Readingaufbereitung
  3592. readingsBeginUpdate($hash);
  3593. ReadingsBulkUpdateValue ($hash, "sqlCmd", $cmd);
  3594. ReadingsBulkUpdateValue ($hash, "sqlResultNumRows", $nrows);
  3595. if ($srf eq "sline") {
  3596. $rowstring =~ s/§/]|[/g;
  3597. $rowstring =~ s/\|°escaped°\|/§/g;
  3598. ReadingsBulkUpdateValue ($hash, "SqlResult", $rowstring);
  3599. } elsif ($srf eq "table") {
  3600. my $res = "<html><table border=2 bordercolor='darkgreen' cellspacing=0>";
  3601. my @rows = split( /§/, $rowstring );
  3602. my $row;
  3603. foreach $row ( @rows ) {
  3604. $row =~ s/\|°escaped°\|/§/g;
  3605. $row =~ s/\|/<\/td><td style='padding-right:5px;padding-left:5px'>/g;
  3606. $res .= "<tr><td style='padding-right:5px;padding-left:5px'>".$row."</td></tr>";
  3607. }
  3608. $row .= $res."</table></html>";
  3609. ReadingsBulkUpdateValue ($hash,"SqlResult", $row);
  3610. } elsif ($srf eq "mline") {
  3611. my $res = "<html>";
  3612. my @rows = split( /§/, $rowstring );
  3613. my $row;
  3614. foreach $row ( @rows ) {
  3615. $row =~ s/\|°escaped°\|/§/g;
  3616. $res .= $row."<br>";
  3617. }
  3618. $row .= $res."</html>";
  3619. ReadingsBulkUpdateValue ($hash, "SqlResult", $row );
  3620. } elsif ($srf eq "separated") {
  3621. my @rows = split( /§/, $rowstring );
  3622. my $bigint = @rows;
  3623. my $numd = ceil(log10($bigint));
  3624. my $formatstr = sprintf('%%%d.%dd', $numd, $numd);
  3625. my $i = 0;
  3626. foreach my $row ( @rows ) {
  3627. $i++;
  3628. $row =~ s/\|°escaped°\|/§/g;
  3629. my $fi = sprintf($formatstr, $i);
  3630. ReadingsBulkUpdateValue ($hash, "SqlResultRow_".$fi, $row);
  3631. }
  3632. } elsif ($srf eq "json") {
  3633. my %result = ();
  3634. my @rows = split( /§/, $rowstring );
  3635. my $bigint = @rows;
  3636. my $numd = ceil(log10($bigint));
  3637. my $formatstr = sprintf('%%%d.%dd', $numd, $numd);
  3638. my $i = 0;
  3639. foreach my $row ( @rows ) {
  3640. $i++;
  3641. $row =~ s/\|°escaped°\|/§/g;
  3642. my $fi = sprintf($formatstr, $i);
  3643. $result{$fi} = $row;
  3644. }
  3645. my $json = toJSON(\%result); # at least fhem.pl 14348 2017-05-22 20:25:06Z
  3646. ReadingsBulkUpdateValue ($hash, "SqlResult", $json);
  3647. }
  3648. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3649. readingsEndUpdate($hash, 1);
  3650. delete($hash->{HELPER}{RUNNING_PID});
  3651. Log3 ($name, 4, "DbRep $name -> BlockingCall count_ParseDone finished");
  3652. return;
  3653. }
  3654. ####################################################################################################
  3655. # nichtblockierende DB-Abfrage get db Metadaten
  3656. ####################################################################################################
  3657. sub dbmeta_DoParse($) {
  3658. my ($string) = @_;
  3659. my @a = split("\\|",$string);
  3660. my $name = $a[0];
  3661. my $hash = $defs{$name};
  3662. my $opt = $a[1];
  3663. my $dbloghash = $hash->{dbloghash};
  3664. my $dbconn = $dbloghash->{dbconn};
  3665. my $db = $hash->{DATABASE};
  3666. my $dbuser = $dbloghash->{dbuser};
  3667. my $dblogname = $dbloghash->{NAME};
  3668. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3669. my $dbmodel = $dbloghash->{MODEL};
  3670. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3671. my ($dbh,$sth,$sql);
  3672. my $err;
  3673. # Background-Startzeit
  3674. my $bst = [gettimeofday];
  3675. Log3 ($name, 4, "DbRep $name -> Start BlockingCall dbmeta_DoParse");
  3676. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3677. if ($@) {
  3678. $err = encode_base64($@,"");
  3679. Log3 ($name, 2, "DbRep $name - $@");
  3680. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3681. return "$name|''|''|''|$err";
  3682. }
  3683. # only for this block because of warnings if details of readings are not set
  3684. no warnings 'uninitialized';
  3685. # Liste der anzuzeigenden Parameter erzeugen, sonst alle ("%"), abhängig von $opt
  3686. my $param = AttrVal($name, "showVariables", "%") if($opt eq "dbvars");
  3687. $param = AttrVal($name, "showSvrInfo", "[A-Z_]") if($opt eq "svrinfo");
  3688. $param = AttrVal($name, "showStatus", "%") if($opt eq "dbstatus");
  3689. $param = "1" if($opt =~ /tableinfo|procinfo/); # Dummy-Eintrag für einen Schleifendurchlauf
  3690. my @parlist = split(",",$param);
  3691. # SQL-Startzeit
  3692. my $st = [gettimeofday];
  3693. my @row_array;
  3694. # due to incompatible changes made in MyQL 5.7.5, see http://johnemb.blogspot.de/2014/09/adding-or-removing-individual-sql-modes.html
  3695. if($dbmodel eq "MYSQL") {
  3696. eval {$dbh->do("SET sql_mode=(SELECT REPLACE(\@\@sql_mode,'ONLY_FULL_GROUP_BY',''));");};
  3697. }
  3698. if ($@) {
  3699. $err = encode_base64($@,"");
  3700. Log3 ($name, 2, "DbRep $name - $@");
  3701. $dbh->disconnect;
  3702. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3703. return "$name|''|''|''|$err";
  3704. }
  3705. if ($opt ne "svrinfo") {
  3706. foreach my $ple (@parlist) {
  3707. if ($opt eq "dbvars") {
  3708. $sql = "show variables like '$ple';";
  3709. } elsif ($opt eq "dbstatus") {
  3710. $sql = "show global status like '$ple';";
  3711. } elsif ($opt eq "tableinfo") {
  3712. $sql = "show Table Status from $db;";
  3713. } elsif ($opt eq "procinfo") {
  3714. $sql = "show full processlist;";
  3715. }
  3716. Log3($name, 4, "DbRep $name - SQL execute: $sql");
  3717. $sth = $dbh->prepare($sql);
  3718. eval {$sth->execute();};
  3719. if ($@) {
  3720. # error bei sql-execute
  3721. $err = encode_base64($@,"");
  3722. Log3 ($name, 2, "DbRep $name - $@");
  3723. $dbh->disconnect;
  3724. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3725. return "$name|''|''|''|$err";
  3726. } else {
  3727. # kein error bei sql-execute
  3728. if ($opt eq "tableinfo") {
  3729. $param = AttrVal($name, "showTableInfo", "[A-Z_]");
  3730. $param =~ s/,/\|/g;
  3731. $param =~ tr/%//d;
  3732. while ( my $line = $sth->fetchrow_hashref()) {
  3733. Log3 ($name, 5, "DbRep $name - SQL result: $line->{Name}, $line->{Version}, $line->{Row_format}, $line->{Rows}, $line->{Avg_row_length}, $line->{Data_length}, $line->{Max_data_length}, $line->{Index_length}, $line->{Data_free}, $line->{Auto_increment}, $line->{Create_time}, $line->{Check_time}, $line->{Collation}, $line->{Checksum}, $line->{Create_options}, $line->{Comment}");
  3734. if($line->{Name} =~ m/($param)/i) {
  3735. push(@row_array, $line->{Name}.".engine ".$line->{Engine}) if($line->{Engine});
  3736. push(@row_array, $line->{Name}.".version ".$line->{Version}) if($line->{Version});
  3737. push(@row_array, $line->{Name}.".row_format ".$line->{Row_format}) if($line->{Row_format});
  3738. push(@row_array, $line->{Name}.".number_of_rows ".$line->{Rows}) if($line->{Rows});
  3739. push(@row_array, $line->{Name}.".avg_row_length ".$line->{Avg_row_length}) if($line->{Avg_row_length});
  3740. push(@row_array, $line->{Name}.".data_length_MB ".sprintf("%.2f",$line->{Data_length}/1024/1024)) if($line->{Data_length});
  3741. push(@row_array, $line->{Name}.".max_data_length_MB ".sprintf("%.2f",$line->{Max_data_length}/1024/1024)) if($line->{Max_data_length});
  3742. push(@row_array, $line->{Name}.".index_length_MB ".sprintf("%.2f",$line->{Index_length}/1024/1024)) if($line->{Index_length});
  3743. push(@row_array, $line->{Name}.".data_index_length_MB ".sprintf("%.2f",($line->{Data_length}+$line->{Index_length})/1024/1024));
  3744. push(@row_array, $line->{Name}.".data_free_MB ".sprintf("%.2f",$line->{Data_free}/1024/1024)) if($line->{Data_free});
  3745. push(@row_array, $line->{Name}.".auto_increment ".$line->{Auto_increment}) if($line->{Auto_increment});
  3746. push(@row_array, $line->{Name}.".create_time ".$line->{Create_time}) if($line->{Create_time});
  3747. push(@row_array, $line->{Name}.".update_time ".$line->{Update_time}) if($line->{Update_time});
  3748. push(@row_array, $line->{Name}.".check_time ".$line->{Check_time}) if($line->{Check_time});
  3749. push(@row_array, $line->{Name}.".collation ".$line->{Collation}) if($line->{Collation});
  3750. push(@row_array, $line->{Name}.".checksum ".$line->{Checksum}) if($line->{Checksum});
  3751. push(@row_array, $line->{Name}.".create_options ".$line->{Create_options}) if($line->{Create_options});
  3752. push(@row_array, $line->{Name}.".comment ".$line->{Comment}) if($line->{Comment});
  3753. }
  3754. }
  3755. } elsif ($opt eq "procinfo") {
  3756. my $res = "<html><table border=2 bordercolor='darkgreen' cellspacing=0>";
  3757. $res .= "<tr><td style='padding-right:5px;padding-left:5px;font-weight:bold'>ID</td>";
  3758. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>USER</td>";
  3759. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>HOST</td>";
  3760. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>DB</td>";
  3761. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>CMD</td>";
  3762. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>TIME_Sec</td>";
  3763. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>STATE</td>";
  3764. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>INFO</td>";
  3765. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>PROGRESS</td></tr>";
  3766. while (my @line = $sth->fetchrow_array()) {
  3767. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  3768. my $row = join("|", @line);
  3769. $row =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\]^_`{|}~//cd;
  3770. $row =~ s/\|/<\/td><td style='padding-right:5px;padding-left:5px'>/g;
  3771. $res .= "<tr><td style='padding-right:5px;padding-left:5px'>".$row."</td></tr>";
  3772. }
  3773. my $tab .= $res."</table></html>";
  3774. push(@row_array, "ProcessList ".$tab);
  3775. } else {
  3776. while (my @line = $sth->fetchrow_array()) {
  3777. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  3778. my $row = join("§", @line);
  3779. $row =~ s/ /_/g;
  3780. @line = split("§", $row);
  3781. push(@row_array, $line[0]." ".$line[1]);
  3782. }
  3783. }
  3784. }
  3785. $sth->finish;
  3786. }
  3787. } else {
  3788. $param =~ s/,/\|/g;
  3789. $param =~ tr/%//d;
  3790. # Log3 ($name, 5, "DbRep $name - showDbInfo: $param");
  3791. if($dbmodel eq 'SQLITE') {
  3792. my $sf = $dbh->sqlite_db_filename();
  3793. if ($@) {
  3794. # error bei sql-execute
  3795. $err = encode_base64($@,"");
  3796. Log3 ($name, 2, "DbRep $name - $@");
  3797. $dbh->disconnect;
  3798. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3799. return "$name|''|''|''|$err";
  3800. } else {
  3801. # kein error bei sql-execute
  3802. my $key = "SQLITE_DB_FILENAME";
  3803. push(@row_array, $key." ".$sf) if($key =~ m/($param)/i);
  3804. }
  3805. my @a = split(' ',qx(du -m $hash->{DATABASE})) if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  3806. my $key = "SQLITE_FILE_SIZE_MB";
  3807. push(@row_array, $key." ".$a[0]) if($key =~ m/($param)/i);
  3808. }
  3809. my $info;
  3810. while( my ($key,$value) = each(%GetInfoType) ) {
  3811. eval { $info = $dbh->get_info($GetInfoType{"$key"}) };
  3812. if ($@) {
  3813. $err = encode_base64($@,"");
  3814. Log3 ($name, 2, "DbRep $name - $@");
  3815. $dbh->disconnect;
  3816. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3817. return "$name|''|''|''|$err";
  3818. } else {
  3819. if($utf8) {
  3820. $info = Encode::encode_utf8($info) if($info);
  3821. }
  3822. push(@row_array, $key." ".$info) if($key =~ m/($param)/i);
  3823. }
  3824. }
  3825. }
  3826. # SQL-Laufzeit ermitteln
  3827. my $rt = tv_interval($st);
  3828. $dbh->disconnect;
  3829. my $rowlist = join('§', @row_array);
  3830. Log3 ($name, 5, "DbRep $name -> row_array: \n@row_array");
  3831. # Daten müssen als Einzeiler zurückgegeben werden
  3832. $rowlist = encode_base64($rowlist,"");
  3833. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_DoParse finished");
  3834. # Background-Laufzeit ermitteln
  3835. my $brt = tv_interval($bst);
  3836. $rt = $rt.",".$brt;
  3837. return "$name|$rowlist|$rt|$opt|0";
  3838. }
  3839. ####################################################################################################
  3840. # Auswertungsroutine der nichtblockierenden DB-Abfrage get db Metadaten
  3841. ####################################################################################################
  3842. sub dbmeta_ParseDone($) {
  3843. my ($string) = @_;
  3844. my @a = split("\\|",$string);
  3845. my $hash = $defs{$a[0]};
  3846. my $name = $hash->{NAME};
  3847. my $rowlist = decode_base64($a[1]);
  3848. my $bt = $a[2];
  3849. my $opt = $a[3];
  3850. my ($rt,$brt) = split(",", $bt);
  3851. my $err = $a[4]?decode_base64($a[4]):undef;
  3852. Log3 ($name, 4, "DbRep $name -> Start BlockingCall dbmeta_ParseDone");
  3853. if ($err) {
  3854. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3855. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3856. delete($hash->{HELPER}{RUNNING_PID});
  3857. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_ParseDone finished");
  3858. return;
  3859. }
  3860. # only for this block because of warnings if details of readings are not set
  3861. no warnings 'uninitialized';
  3862. # Readingaufbereitung
  3863. readingsBeginUpdate($hash);
  3864. my @row_array = split("§", $rowlist);
  3865. Log3 ($name, 5, "DbRep $name - SQL result decoded: \n@row_array") if(@row_array);
  3866. my $pre = "";
  3867. $pre = "VAR_" if($opt eq "dbvars");
  3868. $pre = "STAT_" if($opt eq "dbstatus");
  3869. $pre = "INFO_" if($opt eq "tableinfo");
  3870. foreach my $row (@row_array) {
  3871. my @a = split(" ", $row, 2);
  3872. my $k = $a[0];
  3873. my $v = $a[1];
  3874. ReadingsBulkUpdateValue ($hash, $pre.$k, $v);
  3875. }
  3876. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3877. readingsEndUpdate($hash, 1);
  3878. # InternalTimer(time+0.5, "browser_refresh", $hash, 0);
  3879. delete($hash->{HELPER}{RUNNING_PID});
  3880. Log3 ($name, 4, "DbRep $name -> BlockingCall dbmeta_ParseDone finished");
  3881. return;
  3882. }
  3883. ####################################################################################################
  3884. # optimize Tables MySQL
  3885. ####################################################################################################
  3886. sub DbRep_optimizeTables($) {
  3887. my ($name) = @_;
  3888. my $hash = $defs{$name};
  3889. my $dbloghash = $hash->{dbloghash};
  3890. my $dbconn = $dbloghash->{dbconn};
  3891. my $dbuser = $dbloghash->{dbuser};
  3892. my $dblogname = $dbloghash->{NAME};
  3893. my $dbmodel = $dbloghash->{MODEL};
  3894. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3895. my $dbname = $hash->{DATABASE};
  3896. my $value = 0;
  3897. my ($dbh,$sth,$query,$err,$r,$db_MB_start,$db_MB_end);
  3898. my (%db_tables,@tablenames);
  3899. Log3 ($name, 4, "DbRep $name -> Start BlockingCall DbRep_optimizeTables");
  3900. # Background-Startzeit
  3901. my $bst = [gettimeofday];
  3902. # Verbindung mit DB
  3903. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  3904. if ($@) {
  3905. $err = encode_base64($@,"");
  3906. Log3 ($name, 2, "DbRep $name - $@");
  3907. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  3908. return "$name|''|$err|''|''";
  3909. }
  3910. # SQL-Startzeit
  3911. my $st = [gettimeofday];
  3912. if ($dbmodel =~ /MYSQL/) {
  3913. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  3914. $query = "SHOW TABLE STATUS FROM `$dbname`";
  3915. Log3 ($name, 5, "DbRep $name - current query: $query ");
  3916. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  3917. eval { $sth = $dbh->prepare($query);
  3918. $sth->execute;
  3919. };
  3920. if ($@) {
  3921. $err = encode_base64($@,"");
  3922. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  3923. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  3924. $sth->finish;
  3925. $dbh->disconnect;
  3926. return "$name|''|$err|''|''";
  3927. }
  3928. while ( $value = $sth->fetchrow_hashref()) {
  3929. # verbose 5 logging
  3930. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  3931. foreach my $tk (sort(keys(%$value))) {
  3932. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  3933. }
  3934. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  3935. # check for old MySQL3-Syntax Type=xxx
  3936. if (defined $value->{Type}) {
  3937. # port old index type to index engine, so we can use the index Engine in the rest of the script
  3938. $value->{Engine} = $value->{Type};
  3939. }
  3940. $db_tables{$value->{Name}} = $value;
  3941. }
  3942. @tablenames = sort(keys(%db_tables));
  3943. if (@tablenames < 1) {
  3944. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  3945. Log3 ($name, 2, "DbRep $name - $err");
  3946. $err = encode_base64($@,"");
  3947. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  3948. $sth->finish;
  3949. $dbh->disconnect;
  3950. return "$name|''|$err|''|''";
  3951. }
  3952. # Tabellen optimieren
  3953. $hash->{HELPER}{DBTABLES} = \%db_tables;
  3954. ($err,$db_MB_start,$db_MB_end) = mysql_optimize_tables($hash,$dbh,@tablenames);
  3955. if ($err) {
  3956. $err = encode_base64($err,"");
  3957. return "$name|''|$err|''|''";
  3958. }
  3959. }
  3960. if ($dbmodel =~ /SQLITE/) {
  3961. # Anfangsgröße ermitteln
  3962. $db_MB_start = (split(' ',qx(du -m $hash->{DATABASE})))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  3963. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  3964. $query ="VACUUM";
  3965. Log3 ($name, 5, "DbRep $name - current query: $query ");
  3966. Log3 ($name, 3, "DbRep $name - VACUUM database $dbname....");
  3967. eval {$sth = $dbh->prepare($query);
  3968. $r = $sth->execute();
  3969. };
  3970. if ($@) {
  3971. $err = encode_base64($@,"");
  3972. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! SQLite-Error: ".$@);
  3973. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  3974. $sth->finish;
  3975. $dbh->disconnect;
  3976. return "$name|''|$err|''|''";
  3977. }
  3978. # Endgröße ermitteln
  3979. $db_MB_end = (split(' ',qx(du -m $hash->{DATABASE})))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  3980. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  3981. }
  3982. if ($dbmodel =~ /POSTGRESQL/) {
  3983. # Anfangsgröße ermitteln
  3984. $query = "SELECT pg_size_pretty(pg_database_size('$dbname'))";
  3985. Log3 ($name, 5, "DbRep $name - current query: $query ");
  3986. eval { $sth = $dbh->prepare($query);
  3987. $sth->execute;
  3988. };
  3989. if ($@) {
  3990. $err = encode_base64($@,"");
  3991. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  3992. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  3993. $sth->finish;
  3994. $dbh->disconnect;
  3995. return "$name|''|$err|''|''";
  3996. }
  3997. $value = $sth->fetchrow();
  3998. $value =~ tr/MB//d;
  3999. $db_MB_start = sprintf("%.2f",$value);
  4000. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  4001. Log3 ($name, 3, "DbRep $name - VACUUM database $dbname....");
  4002. $query = "vacuum history";
  4003. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4004. eval {$sth = $dbh->prepare($query);
  4005. $sth->execute();
  4006. };
  4007. if ($@) {
  4008. $err = encode_base64($@,"");
  4009. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  4010. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  4011. $sth->finish;
  4012. $dbh->disconnect;
  4013. return "$name|''|$err|''|''";
  4014. }
  4015. # Endgröße ermitteln
  4016. $query = "SELECT pg_size_pretty(pg_database_size('$dbname'))";
  4017. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4018. eval { $sth = $dbh->prepare($query);
  4019. $sth->execute;
  4020. };
  4021. if ($@) {
  4022. $err = encode_base64($@,"");
  4023. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  4024. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  4025. $sth->finish;
  4026. $dbh->disconnect;
  4027. return "$name|''|$err|''|''";
  4028. }
  4029. $value = $sth->fetchrow();
  4030. $value =~ tr/MB//d;
  4031. $db_MB_end = sprintf("%.2f",$value);
  4032. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  4033. }
  4034. $sth->finish;
  4035. $dbh->disconnect;
  4036. # SQL-Laufzeit ermitteln
  4037. my $rt = tv_interval($st);
  4038. # Background-Laufzeit ermitteln
  4039. my $brt = tv_interval($bst);
  4040. $rt = $rt.",".$brt;
  4041. Log3 ($name, 3, "DbRep $name - Optimize tables of database $dbname finished, total time used: ".sprintf("%.0f",$brt)." sec.");
  4042. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  4043. return "$name|$rt|''|$db_MB_start|$db_MB_end";
  4044. }
  4045. ####################################################################################################
  4046. # Auswertungsroutine optimze tables
  4047. ####################################################################################################
  4048. sub OptimizeDone($) {
  4049. my ($string) = @_;
  4050. my @a = split("\\|",$string);
  4051. my $hash = $defs{$a[0]};
  4052. my $bt = $a[1];
  4053. my ($rt,$brt) = split(",", $bt);
  4054. my $err = $a[2]?decode_base64($a[2]):undef;
  4055. my $db_MB_start = $a[3];
  4056. my $db_MB_end = $a[4];
  4057. my $name = $hash->{NAME};
  4058. Log3 ($name, 4, "DbRep $name -> Start BlockingCall OptimizeDone");
  4059. delete($hash->{HELPER}{RUNNING_OPTIMIZE});
  4060. if ($err) {
  4061. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4062. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4063. Log3 ($name, 4, "DbRep $name -> BlockingCall OptimizeDone finished");
  4064. return;
  4065. }
  4066. # only for this block because of warnings if details of readings are not set
  4067. no warnings 'uninitialized';
  4068. my $state = "optimize tables finished";
  4069. readingsBeginUpdate($hash);
  4070. ReadingsBulkUpdateValue($hash, "SizeDbBegin_MB", $db_MB_start);
  4071. ReadingsBulkUpdateValue($hash, "SizeDbEnd_MB", $db_MB_end);
  4072. ReadingsBulkUpdateTimeState($hash,$brt,undef,$state);
  4073. readingsEndUpdate($hash, 1);
  4074. Log3 ($name, 3, "DbRep $name - Optimize tables finished successfully. ");
  4075. Log3 ($name, 4, "DbRep $name -> BlockingCall OptimizeDone finished");
  4076. return;
  4077. }
  4078. ####################################################################################################
  4079. # nicht blockierende Dump-Routine für MySQL (clientSide)
  4080. ####################################################################################################
  4081. sub mysql_DoDumpClientSide($) {
  4082. my ($name) = @_;
  4083. my $hash = $defs{$name};
  4084. my $dbloghash = $hash->{dbloghash};
  4085. my $dbconn = $dbloghash->{dbconn};
  4086. my $dbuser = $dbloghash->{dbuser};
  4087. my $dblogname = $dbloghash->{NAME};
  4088. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4089. my $dbname = $hash->{DATABASE};
  4090. my $dump_path_def = $attr{global}{modpath}."/log/";
  4091. my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def);
  4092. $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/);
  4093. my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0);
  4094. my $memory_limit = AttrVal($name, "dumpMemlimit", 100000);
  4095. my $my_comment = AttrVal($name, "dumpComment", "");
  4096. my $dumpspeed = AttrVal($name, "dumpSpeed", 10000);
  4097. my $ebd = AttrVal($name, "executeBeforeDump", undef);
  4098. my $ead = AttrVal($name, "executeAfterDump", undef);
  4099. my $mysql_commentstring = "-- ";
  4100. my $character_set = "utf8";
  4101. my $repver = $hash->{VERSION};
  4102. my $sql_text = '';
  4103. my $sql_file = '';
  4104. my $dbpraefix = "";
  4105. my ($dbh,$sth,$tablename,$sql_create,$rct,$insert,$first_insert,$backupfile,$drc,$drh,
  4106. $sql_daten,$inhalt,$filesize,$totalrecords,$status_start,$status_end,$err,$db_MB_start,$db_MB_end);
  4107. my (@ar,@tablerecords,@tablenames,@tables,@ergebnis);
  4108. my (%db_tables);
  4109. # Background-Startzeit
  4110. my $bst = [gettimeofday];
  4111. Log3 ($name, 4, "DbRep $name -> Start BlockingCall mysql_DoDumpClientSide");
  4112. Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname'");
  4113. ##################### Beginn Dump ########################
  4114. ##############################################################
  4115. undef(%db_tables);
  4116. # Startzeit ermitteln
  4117. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  4118. $Jahr += 1900;
  4119. $Monat += 1;
  4120. $Jahrestag += 1;
  4121. my $CTIME_String = strftime "%Y-%m-%d %T",localtime(time);
  4122. my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten);
  4123. my $starttime = sprintf("%02d",$Monatstag).".".sprintf("%02d",$Monat).".".$Jahr." ".sprintf("%02d",$Stunden).":".sprintf("%02d",$Minuten);
  4124. my $fieldlist = "";
  4125. # Verbindung mit DB
  4126. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  4127. if ($@) {
  4128. $err = encode_base64($@,"");
  4129. Log3 ($name, 2, "DbRep $name - $@");
  4130. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4131. return "$name|''|$err|''|''|''|''|''|''";
  4132. }
  4133. # SQL-Startzeit
  4134. my $st = [gettimeofday];
  4135. ##################### Mysql-Version ermitteln ########################
  4136. eval { $sth = $dbh->prepare("SELECT VERSION()");
  4137. $sth->execute;
  4138. };
  4139. if ($@) {
  4140. $err = encode_base64($@,"");
  4141. Log3 ($name, 2, "DbRep $name - $@");
  4142. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4143. $dbh->disconnect;
  4144. return "$name|''|$err|''|''|''|''|''|''";
  4145. }
  4146. my @mysql_version = $sth->fetchrow;
  4147. my @v = split(/\./,$mysql_version[0]);
  4148. if($v[0] >= 5 || ($v[0] >= 4 && $v[1] >= 1) ) {
  4149. # mysql Version >= 4.1
  4150. $sth = $dbh->prepare("SET NAMES '".$character_set."'");
  4151. $sth->execute;
  4152. # get standard encoding of MySQl-Server
  4153. $sth = $dbh->prepare("SHOW VARIABLES LIKE 'character_set_connection'");
  4154. $sth->execute;
  4155. @ar = $sth->fetchrow;
  4156. $character_set = $ar[1];
  4157. } else {
  4158. # mysql Version < 4.1 -> no SET NAMES available
  4159. # get standard encoding of MySQl-Server
  4160. $sth = $dbh->prepare("SHOW VARIABLES LIKE 'character_set'");
  4161. $sth->execute;
  4162. @ar = $sth->fetchrow;
  4163. if (defined($ar[1])) { $character_set=$ar[1]; }
  4164. }
  4165. Log3 ($name, 3, "DbRep $name - Characterset of collection and backup file set to $character_set. ");
  4166. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  4167. undef(@tables);
  4168. undef(@tablerecords);
  4169. my %db_tables_views;
  4170. my $t = 0;
  4171. my $r = 0;
  4172. my $st_e = "\n";
  4173. my $value = 0;
  4174. my $engine = '';
  4175. my $query ="SHOW TABLE STATUS FROM `$dbname`";
  4176. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4177. if ($dbpraefix ne "") {
  4178. $query.=" LIKE '$dbpraefix%'";
  4179. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname with prefix $dbpraefix....");
  4180. } else {
  4181. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  4182. }
  4183. eval { $sth = $dbh->prepare($query);
  4184. $sth->execute;
  4185. };
  4186. if ($@) {
  4187. $err = encode_base64($@,"");
  4188. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  4189. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4190. $dbh->disconnect;
  4191. return "$name|''|$err|''|''|''|''|''|''";
  4192. }
  4193. while ( $value = $sth->fetchrow_hashref()) {
  4194. $value->{skip_data} = 0; #defaut -> backup data of table
  4195. # verbose 5 logging
  4196. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  4197. foreach my $tk (sort(keys(%$value))) {
  4198. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  4199. }
  4200. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  4201. # decide if we need to skip the data while dumping (VIEWs and MEMORY)
  4202. # check for old MySQL3-Syntax Type=xxx
  4203. if (defined $value->{Type}) {
  4204. # port old index type to index engine, so we can use the index Engine in the rest of the script
  4205. $value->{Engine} = $value->{Type};
  4206. $engine = uc($value->{Type});
  4207. if ($engine eq "MEMORY") {
  4208. $value->{skip_data} = 1;
  4209. }
  4210. }
  4211. # check for > MySQL3 Engine = xxx
  4212. if (defined $value->{Engine}) {
  4213. $engine = uc($value->{Engine});
  4214. if ($engine eq "MEMORY") {
  4215. $value->{skip_data} = 1;
  4216. }
  4217. }
  4218. # check for Views - if it is a view the comment starts with "VIEW"
  4219. if (defined $value->{Comment} && uc(substr($value->{Comment},0,4)) eq 'VIEW') {
  4220. $value->{skip_data} = 1;
  4221. $value->{Engine} = 'VIEW';
  4222. $value->{Update_time} = '';
  4223. $db_tables_views{$value->{Name}} = $value;
  4224. } else {
  4225. $db_tables{$value->{Name}} = $value;
  4226. }
  4227. # cast indexes to int, cause they are used for builing the statusline
  4228. $value->{Rows} += 0;
  4229. $value->{Data_length} += 0;
  4230. $value->{Index_length} += 0;
  4231. }
  4232. $sth->finish;
  4233. @tablenames = sort(keys(%db_tables));
  4234. # add VIEW at the end as they need all tables to be created before
  4235. @tablenames = (@tablenames,sort(keys(%db_tables_views)));
  4236. %db_tables = (%db_tables,%db_tables_views);
  4237. $tablename = '';
  4238. if (@tablenames < 1) {
  4239. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  4240. Log3 ($name, 2, "DbRep $name - $err");
  4241. $err = encode_base64($@,"");
  4242. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4243. $dbh->disconnect;
  4244. return "$name|''|$err|''|''|''|''|''|''";
  4245. }
  4246. if($optimize_tables_beforedump) {
  4247. # Tabellen optimieren vor dem Dump
  4248. $hash->{HELPER}{DBTABLES} = \%db_tables;
  4249. ($err,$db_MB_start,$db_MB_end) = mysql_optimize_tables($hash,$dbh,@tablenames);
  4250. if ($err) {
  4251. $err = encode_base64($err,"");
  4252. return "$name|''|$err|''|''|''|''|''|''";
  4253. }
  4254. }
  4255. # Tabelleneigenschaften für SQL-File ermitteln
  4256. $st_e .= "-- TABLE-INFO\n";
  4257. foreach $tablename (@tablenames) {
  4258. my $dump_table = 1;
  4259. if ($dbpraefix ne "") {
  4260. if (substr($tablename,0,length($dbpraefix)) ne $dbpraefix) {
  4261. # exclude table from backup because it doesn't fit to praefix
  4262. $dump_table = 0;
  4263. }
  4264. }
  4265. if ($dump_table == 1) {
  4266. # how many rows
  4267. $sql_create = "SELECT count(*) FROM `$tablename`";
  4268. eval { $sth = $dbh->prepare($sql_create);
  4269. $sth->execute;
  4270. };
  4271. if ($@) {
  4272. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  4273. Log3 ($name, 2, "DbRep $name - $err");
  4274. $err = encode_base64($@,"");
  4275. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4276. $dbh->disconnect;
  4277. return "$name|''|$err|''|''|''|''|''|''";
  4278. }
  4279. $db_tables{$tablename}{Rows} = $sth->fetchrow;
  4280. $sth->finish;
  4281. $r += $db_tables{$tablename}{Rows};
  4282. push(@tables,$db_tables{$tablename}{Name}); # add tablename to backuped tables
  4283. $t++;
  4284. if (!defined $db_tables{$tablename}{Update_time}) {
  4285. $db_tables{$tablename}{Update_time} = 0;
  4286. }
  4287. $st_e .= $mysql_commentstring."TABLE: $db_tables{$tablename}{Name} | Rows: $db_tables{$tablename}{Rows} | Length: ".($db_tables{$tablename}{Data_length}+$db_tables{$tablename}{Index_length})." | Engine: $db_tables{$tablename}{Engine}\n";
  4288. if($db_tables{$tablename}{Name} eq "current") {
  4289. $drc = $db_tables{$tablename}{Rows};
  4290. }
  4291. if($db_tables{$tablename}{Name} eq "history") {
  4292. $drh = $db_tables{$tablename}{Rows};
  4293. }
  4294. }
  4295. }
  4296. $st_e .= "-- EOF TABLE-INFO";
  4297. Log3 ($name, 3, "DbRep $name - Found ".(@tables)." tables with $r records.");
  4298. # AUFBAU der Statuszeile in SQL-File:
  4299. # -- Status | tabellenzahl | datensaetze | Datenbankname | Kommentar | MySQLVersion | Charset | EXTINFO
  4300. #
  4301. $status_start = $mysql_commentstring."Status | Tables: $t | Rows: $r ";
  4302. $status_end = "| DB: $dbname | Comment: $my_comment | MySQL-Version: $mysql_version[0] ";
  4303. $status_end .= "| Charset: $character_set $st_e\n".
  4304. $mysql_commentstring."Dump created on $CTIME_String by DbRep-Version $repver\n".$mysql_commentstring;
  4305. $sql_text = $status_start.$status_end;
  4306. # neues SQL-Ausgabefile anlegen
  4307. ($sql_text,$first_insert,$sql_file,$backupfile,$err) = NewDumpFilename($sql_text,$dump_path,$dbname,$time_stamp,$character_set);
  4308. if ($err) {
  4309. Log3 ($name, 2, "DbRep $name - $err");
  4310. $err = encode_base64($err,"");
  4311. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4312. return "$name|''|$err|''|''|''|''|''|''";
  4313. } else {
  4314. Log3 ($name, 5, "DbRep $name - New dumpfile $sql_file has been created.");
  4315. }
  4316. ##################### jede einzelne Tabelle dumpen ########################
  4317. $totalrecords = 0;
  4318. foreach $tablename (@tables) {
  4319. # first get CREATE TABLE Statement
  4320. if($dbpraefix eq "" || ($dbpraefix ne "" && substr($tablename,0,length($dbpraefix)) eq $dbpraefix)) {
  4321. Log3 ($name, 3, "DbRep $name - Dumping table $tablename (Type ".$db_tables{$tablename}{Engine}."):");
  4322. $a = "\n\n$mysql_commentstring\n$mysql_commentstring"."Table structure for table `$tablename`\n$mysql_commentstring\n";
  4323. if ($db_tables{$tablename}{Engine} ne 'VIEW' ) {
  4324. $a .= "DROP TABLE IF EXISTS `$tablename`;\n";
  4325. } else {
  4326. $a .= "DROP VIEW IF EXISTS `$tablename`;\n";
  4327. }
  4328. $sql_text .= $a;
  4329. $sql_create = "SHOW CREATE TABLE `$tablename`";
  4330. Log3 ($name, 5, "DbRep $name - current query: $sql_create ");
  4331. eval { $sth = $dbh->prepare($sql_create);
  4332. $sth->execute;
  4333. };
  4334. if ($@) {
  4335. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  4336. Log3 ($name, 2, "DbRep $name - $err");
  4337. $err = encode_base64($@,"");
  4338. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4339. $dbh->disconnect;
  4340. return "$name|''|$err|''|''|''|''|''|''";
  4341. }
  4342. @ergebnis = $sth->fetchrow;
  4343. $sth->finish;
  4344. $a = $ergebnis[1].";\n";
  4345. if (length($a) < 10) {
  4346. $err = "Fatal error! Couldn't read CREATE-Statement of table `$tablename`! This backup might be incomplete! Check your database for errors. MySQL-Error: ".$DBI::errstr;
  4347. Log3 ($name, 2, "DbRep $name - $err");
  4348. } else {
  4349. $sql_text .= $a;
  4350. # verbose 5 logging
  4351. Log3 ($name, 5, "DbRep $name - Create-SQL found:\n$a");
  4352. }
  4353. if ($db_tables{$tablename}{skip_data} == 0) {
  4354. $sql_text .= "\n$mysql_commentstring\n$mysql_commentstring"."Dumping data for table `$tablename`\n$mysql_commentstring\n";
  4355. $sql_text .= "/*!40000 ALTER TABLE `$tablename` DISABLE KEYS */;";
  4356. WriteToDumpFile($sql_text,$sql_file);
  4357. $sql_text = "";
  4358. # build fieldlist
  4359. $fieldlist = "(";
  4360. $sql_create = "SHOW FIELDS FROM `$tablename`";
  4361. Log3 ($name, 5, "DbRep $name - current query: $sql_create ");
  4362. eval { $sth = $dbh->prepare($sql_create);
  4363. $sth->execute;
  4364. };
  4365. if ($@) {
  4366. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  4367. Log3 ($name, 2, "DbRep $name - $err");
  4368. $err = encode_base64($@,"");
  4369. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4370. $dbh->disconnect;
  4371. return "$name|''|$err|''|''|''|''|''|''";
  4372. }
  4373. while (@ar = $sth->fetchrow) {
  4374. $fieldlist .= "`".$ar[0]."`,";
  4375. }
  4376. $sth->finish;
  4377. # verbose 5 logging
  4378. Log3 ($name, 5, "DbRep $name - Fieldlist found: $fieldlist");
  4379. # remove trailing ',' and add ')'
  4380. $fieldlist = substr($fieldlist,0,length($fieldlist)-1).")";
  4381. # how many rows
  4382. $rct = $db_tables{$tablename}{Rows};
  4383. Log3 ($name, 5, "DbRep $name - Number entries of table $tablename: $rct");
  4384. # create insert Statements
  4385. for (my $ttt = 0; $ttt < $rct; $ttt += $dumpspeed) {
  4386. # default beginning for INSERT-String
  4387. $insert = "INSERT INTO `$tablename` $fieldlist VALUES (";
  4388. $first_insert = 0;
  4389. # get rows (parts)
  4390. $sql_daten = "SELECT * FROM `$tablename` LIMIT ".$ttt.",".$dumpspeed.";";
  4391. eval { $sth = $dbh->prepare($sql_daten);
  4392. $sth->execute;
  4393. };
  4394. if ($@) {
  4395. $err = "Fatal error sending Query '".$sql_daten."' ! MySQL-Error: ".$@;
  4396. Log3 ($name, 2, "DbRep $name - $err");
  4397. $err = encode_base64($@,"");
  4398. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4399. $dbh->disconnect;
  4400. return "$name|''|$err|''|''|''|''|''|''";
  4401. }
  4402. while ( @ar = $sth->fetchrow) {
  4403. #Start the insert
  4404. if($first_insert == 0) {
  4405. $a = "\n$insert";
  4406. } else {
  4407. $a = "\n(";
  4408. }
  4409. # quote all values
  4410. foreach $inhalt(@ar) { $a .= $dbh->quote($inhalt).","; }
  4411. # remove trailing ',' and add end-sql
  4412. $a = substr($a,0, length($a)-1).");";
  4413. $sql_text .= $a;
  4414. if($memory_limit > 0 && length($sql_text) > $memory_limit) {
  4415. ($filesize,$err) = WriteToDumpFile($sql_text,$sql_file);
  4416. # Log3 ($name, 5, "DbRep $name - Memory limit '$memory_limit' exceeded. Wrote to '$sql_file'. Filesize: '".byte_output($filesize)."'");
  4417. $sql_text = "";
  4418. }
  4419. }
  4420. $sth->finish;
  4421. }
  4422. $sql_text .= "\n/*!40000 ALTER TABLE `$tablename` ENABLE KEYS */;\n";
  4423. }
  4424. # write sql commands to file
  4425. ($filesize,$err) = WriteToDumpFile($sql_text,$sql_file);
  4426. $sql_text = "";
  4427. if ($db_tables{$tablename}{skip_data} == 0) {
  4428. Log3 ($name, 3, "DbRep $name - $rct records inserted (size of backupfile: ".byte_output($filesize).")");
  4429. $totalrecords += $rct;
  4430. } else {
  4431. Log3 ($name, 3, "DbRep $name - Dumping structure of $tablename (Type ".$db_tables{$tablename}{Engine}." ) (size of backupfile: ".byte_output($filesize).")");
  4432. }
  4433. }
  4434. }
  4435. # end
  4436. WriteToDumpFile("\nSET FOREIGN_KEY_CHECKS=1;\n",$sql_file);
  4437. ($filesize,$err) = WriteToDumpFile($mysql_commentstring."EOB\n",$sql_file);
  4438. # Datenbankverbindung schliessen
  4439. $sth->finish() if (defined $sth);
  4440. $dbh->disconnect();
  4441. # SQL-Laufzeit ermitteln
  4442. my $rt = tv_interval($st);
  4443. # Dumpfile per FTP senden
  4444. my ($ftperr,$ftpmsg) = sendftp($hash,$sql_file);
  4445. my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0;
  4446. # alte Dumpfiles löschen
  4447. my @fd = deldumpfiles($hash,$sql_file);
  4448. my $bfd = join(", ", @fd );
  4449. $bfd = $bfd?encode_base64($bfd,""):0;
  4450. # Background-Laufzeit ermitteln
  4451. my $brt = tv_interval($bst);
  4452. $rt = $rt.",".$brt;
  4453. Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname, total time used: ".sprintf("%.0f",$brt)." sec.");
  4454. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4455. return "$name|$rt|''|$sql_file|$drc|$drh|$filesize|$ftp|$bfd";
  4456. }
  4457. ####################################################################################################
  4458. # nicht blockierende Dump-Routine für MySQL (serverSide)
  4459. ####################################################################################################
  4460. sub mysql_DoDumpServerSide($) {
  4461. my ($name) = @_;
  4462. my $hash = $defs{$name};
  4463. my $dbloghash = $hash->{dbloghash};
  4464. my $dbconn = $dbloghash->{dbconn};
  4465. my $dbuser = $dbloghash->{dbuser};
  4466. my $dblogname = $dbloghash->{NAME};
  4467. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4468. my $dbname = $hash->{DATABASE};
  4469. my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0);
  4470. my $dump_path_rem = AttrVal($name, "dumpDirRemote", "./");
  4471. $dump_path_rem = $dump_path_rem."/" unless($dump_path_rem =~ m/\/$/);
  4472. my $ebd = AttrVal($name, "executeBeforeDump", undef);
  4473. my $ead = AttrVal($name, "executeAfterDump", undef);
  4474. my $table = "history";
  4475. my ($dbh,$sth,$err,$db_MB_start,$db_MB_end,$drh);
  4476. my (%db_tables,@tablenames);
  4477. Log3 ($name, 4, "DbRep $name -> Start BlockingCall mysql_DoDumpServerSide");
  4478. # Background-Startzeit
  4479. my $bst = [gettimeofday];
  4480. # Verbindung mit DB
  4481. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  4482. if ($@) {
  4483. $err = encode_base64($@,"");
  4484. Log3 ($name, 2, "DbRep $name - $@");
  4485. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpServerSide finished");
  4486. return "$name|''|$err|''|''|''|''|''|''";
  4487. }
  4488. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  4489. my $value = 0;
  4490. my $query ="SHOW TABLE STATUS FROM `$dbname`";
  4491. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4492. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  4493. eval { $sth = $dbh->prepare($query);
  4494. $sth->execute;
  4495. };
  4496. if ($@) {
  4497. $err = encode_base64($@,"");
  4498. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  4499. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4500. $dbh->disconnect;
  4501. return "$name|''|$err|''|''|''|''|''|''";
  4502. }
  4503. while ( $value = $sth->fetchrow_hashref()) {
  4504. # verbose 5 logging
  4505. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  4506. foreach my $tk (sort(keys(%$value))) {
  4507. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  4508. }
  4509. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  4510. # check for old MySQL3-Syntax Type=xxx
  4511. if (defined $value->{Type}) {
  4512. # port old index type to index engine, so we can use the index Engine in the rest of the script
  4513. $value->{Engine} = $value->{Type};
  4514. }
  4515. $db_tables{$value->{Name}} = $value;
  4516. }
  4517. $sth->finish;
  4518. @tablenames = sort(keys(%db_tables));
  4519. if (@tablenames < 1) {
  4520. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  4521. Log3 ($name, 2, "DbRep $name - $err");
  4522. $err = encode_base64($@,"");
  4523. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpClientSide finished");
  4524. $dbh->disconnect;
  4525. return "$name|''|$err|''|''|''|''|''|''";
  4526. }
  4527. if($optimize_tables_beforedump) {
  4528. # Tabellen optimieren vor dem Dump
  4529. $hash->{HELPER}{DBTABLES} = \%db_tables;
  4530. ($err,$db_MB_start,$db_MB_end) = mysql_optimize_tables($hash,$dbh,@tablenames);
  4531. if ($err) {
  4532. $err = encode_base64($err,"");
  4533. return "$name|''|$err|''|''|''|''|''|''";
  4534. }
  4535. }
  4536. Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname', table '$table'");
  4537. # Startzeit ermitteln
  4538. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  4539. $Jahr += 1900;
  4540. $Monat += 1;
  4541. $Jahrestag += 1;
  4542. my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten);
  4543. my $bfile = $dbname."_".$table."_".$time_stamp.".csv";
  4544. Log3 ($name, 5, "DbRep $name - Use Outfile: $dump_path_rem$bfile");
  4545. # SQL-Startzeit
  4546. my $st = [gettimeofday];
  4547. my $sql = "SELECT * FROM history INTO OUTFILE '$dump_path_rem$bfile' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n'; ";
  4548. eval {$sth = $dbh->prepare($sql);
  4549. $drh = $sth->execute();
  4550. };
  4551. if ($@) {
  4552. # error bei sql-execute
  4553. $err = encode_base64($@,"");
  4554. Log3 ($name, 2, "DbRep $name - $@");
  4555. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpServerSide finished");
  4556. $dbh->disconnect;
  4557. return "$name|''|$err|''|''|''|''|''|''";
  4558. }
  4559. $sth->finish;
  4560. $dbh->disconnect;
  4561. # SQL-Laufzeit ermitteln
  4562. my $rt = tv_interval($st);
  4563. # Größe Dumpfile ermitteln ("dumpDirRemote" muß auf "dumpDirLocal" gemountet sein)
  4564. my $dump_path_def = $attr{global}{modpath}."/log/";
  4565. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  4566. $dump_path_loc = $dump_path_loc."/" unless($dump_path_loc =~ m/\/$/);
  4567. my $filesize = (stat($dump_path_loc.$bfile))[7]?(stat($dump_path_loc.$bfile))[7]:"n.a.";
  4568. Log3 ($name, 3, "DbRep $name - Number of exported datasets: $drh");
  4569. Log3 ($name, 3, "DbRep $name - Size of backupfile: ".byte_output($filesize));
  4570. # Dumpfile per FTP senden
  4571. my ($ftperr,$ftpmsg) = sendftp($hash,$bfile);
  4572. my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0;
  4573. # alte Dumpfiles löschen
  4574. my @fd = deldumpfiles($hash,$bfile);
  4575. my $bfd = join(", ", @fd );
  4576. $bfd = $bfd?encode_base64($bfd,""):0;
  4577. # Background-Laufzeit ermitteln
  4578. my $brt = tv_interval($bst);
  4579. $rt = $rt.",".$brt;
  4580. Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname - total time used: ".sprintf("%.0f",$brt)." seconds");
  4581. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_DoDumpServerSide finished");
  4582. return "$name|$rt|''|$dump_path_rem$bfile|n.a.|$drh|$filesize|$ftp|$bfd";
  4583. }
  4584. ####################################################################################################
  4585. # Auswertungsroutine der nicht blockierenden DB-Funktion Dump
  4586. ####################################################################################################
  4587. sub DumpDone($) {
  4588. my ($string) = @_;
  4589. my @a = split("\\|",$string);
  4590. my $hash = $defs{$a[0]};
  4591. my $bt = $a[1];
  4592. my ($rt,$brt) = split(",", $bt);
  4593. my $err = $a[2]?decode_base64($a[2]):undef;
  4594. my $bfile = $a[3];
  4595. my $drc = $a[4];
  4596. my $drh = $a[5];
  4597. my $fs = $a[6];
  4598. my $ftp = $a[7]?decode_base64($a[7]):undef;
  4599. my $bfd = $a[8]?decode_base64($a[8]):undef;
  4600. my $name = $hash->{NAME};
  4601. my $erread;
  4602. Log3 ($name, 4, "DbRep $name -> Start BlockingCall DumpDone");
  4603. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4604. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4605. if ($err) {
  4606. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4607. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4608. Log3 ($name, 4, "DbRep $name -> BlockingCall DumpDone finished");
  4609. return;
  4610. }
  4611. # only for this block because of warnings if details of readings are not set
  4612. no warnings 'uninitialized';
  4613. readingsBeginUpdate($hash);
  4614. ReadingsBulkUpdateValue($hash, "DumpFileCreated", $bfile);
  4615. ReadingsBulkUpdateValue($hash, "DumpFileCreatedSize", $fs);
  4616. ReadingsBulkUpdateValue($hash, "DumpFilesDeleted", $bfd);
  4617. ReadingsBulkUpdateValue($hash, "DumpRowsCurrrent", $drc);
  4618. ReadingsBulkUpdateValue($hash, "DumpRowsHistory", $drh);
  4619. ReadingsBulkUpdateValue($hash, "FTP_Message", $ftp) if($ftp);
  4620. readingsEndUpdate($hash, 1);
  4621. # Befehl nach Dump ausführen
  4622. my $ead = AttrVal($name, "executeAfterDump", undef);
  4623. if($ead) {
  4624. Log3 ($name, 4, "DbRep $name - execute command after dump: '$ead' ");
  4625. $err = AnalyzeCommandChain(undef, $ead);
  4626. if ($err) {
  4627. Log3 ($name, 2, "DbRep $name - $err");
  4628. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4629. $erread = "Warning - Database backup finished but command after dump not successful";
  4630. }
  4631. }
  4632. my $state = $erread?$erread:"Database backup finished";
  4633. readingsBeginUpdate($hash);
  4634. ReadingsBulkUpdateTimeState($hash,$brt,undef,$state);
  4635. readingsEndUpdate($hash, 1);
  4636. Log3 ($name, 3, "DbRep $name - Database dump finished successfully. ");
  4637. Log3 ($name, 4, "DbRep $name -> BlockingCall DumpDone finished");
  4638. return;
  4639. }
  4640. ####################################################################################################
  4641. # Restore MySQL (serverSide)
  4642. ####################################################################################################
  4643. sub mysql_RestoreServerSide($) {
  4644. my ($string) = @_;
  4645. my ($name, $bfile) = split("\\|", $string);
  4646. my $hash = $defs{$name};
  4647. my $dbloghash = $hash->{dbloghash};
  4648. my $dbconn = $dbloghash->{dbconn};
  4649. my $dbuser = $dbloghash->{dbuser};
  4650. my $dblogname = $dbloghash->{NAME};
  4651. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4652. my $dbname = $hash->{DATABASE};
  4653. my $dump_path_rem = AttrVal($name, "dumpDirRemote", "./");
  4654. $dump_path_rem = $dump_path_rem."/" unless($dump_path_rem =~ m/\/$/);
  4655. my $table = "history";
  4656. my ($dbh,$sth,$err,$drh);
  4657. Log3 ($name, 4, "DbRep $name -> Start BlockingCall mysql_RestoreServerSide");
  4658. # Background-Startzeit
  4659. my $bst = [gettimeofday];
  4660. # Verbindung mit DB
  4661. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  4662. if ($@) {
  4663. $err = encode_base64($@,"");
  4664. Log3 ($name, 2, "DbRep $name - $@");
  4665. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished");
  4666. return "$name|''|$err|''|''|''|''";
  4667. }
  4668. Log3 ($name, 3, "DbRep $name - Starting restore of database '$dbname', table '$table'.");
  4669. # SQL-Startzeit
  4670. my $st = [gettimeofday];
  4671. my $sql = "LOAD DATA CONCURRENT INFILE '$dump_path_rem$bfile' IGNORE INTO TABLE $table FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n'; ";
  4672. eval {$sth = $dbh->prepare($sql);
  4673. $drh = $sth->execute();
  4674. };
  4675. if ($@) {
  4676. # error bei sql-execute
  4677. $err = encode_base64($@,"");
  4678. Log3 ($name, 2, "DbRep $name - $@");
  4679. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished");
  4680. $dbh->disconnect;
  4681. return "$name|''|$err|''|''|''|''";
  4682. }
  4683. $sth->finish;
  4684. $dbh->disconnect;
  4685. # SQL-Laufzeit ermitteln
  4686. my $rt = tv_interval($st);
  4687. # Background-Laufzeit ermitteln
  4688. my $brt = tv_interval($bst);
  4689. $rt = $rt.",".$brt;
  4690. Log3 ($name, 3, "DbRep $name - Restore of $dump_path_rem$bfile into '$dbname', '$table' finished - total time used: ".sprintf("%.0f",$brt)." sec for $drh datasets.");
  4691. Log3 ($name, 3, "DbRep $name - Number of imported datasets: $drh.");
  4692. Log3 ($name, 4, "DbRep $name -> BlockingCall mysql_RestoreServerSide finished");
  4693. return "$name|$rt|''|$dump_path_rem$bfile|$drh";
  4694. }
  4695. ####################################################################################################
  4696. # Auswertungsroutine Restore
  4697. ####################################################################################################
  4698. sub RestoreDone($) {
  4699. my ($string) = @_;
  4700. my @a = split("\\|",$string);
  4701. my $hash = $defs{$a[0]};
  4702. my $bt = $a[1];
  4703. my ($rt,$brt) = split(",", $bt);
  4704. my $err = $a[2]?decode_base64($a[2]):undef;
  4705. my $bfile = $a[3];
  4706. my $drh = $a[4];
  4707. my $name = $hash->{NAME};
  4708. Log3 ($name, 4, "DbRep $name -> Start BlockingCall RestoreDone");
  4709. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4710. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4711. if ($err) {
  4712. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4713. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4714. Log3 ($name, 4, "DbRep $name -> BlockingCall RestoreDone finished");
  4715. return;
  4716. }
  4717. my $state = "Restore of $bfile finished";
  4718. readingsBeginUpdate($hash);
  4719. ReadingsBulkUpdateValue($hash, "RestoreRowsHistory", $drh);
  4720. ReadingsBulkUpdateTimeState($hash,$brt,undef,$state);
  4721. readingsEndUpdate($hash, 1);
  4722. Log3 ($name, 3, "DbRep $name - Database restore finished successfully. ");
  4723. Log3 ($name, 4, "DbRep $name -> BlockingCall RestoreDone finished");
  4724. return;
  4725. }
  4726. ####################################################################################################
  4727. # Abbruchroutine Timeout Restore
  4728. ####################################################################################################
  4729. sub RestoreAborted(@) {
  4730. my ($hash,$cause) = @_;
  4731. my $name = $hash->{NAME};
  4732. my $dbh = $hash->{DBH} if ($hash->{DBH});
  4733. $cause = $cause?$cause:"Timeout: process terminated";
  4734. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4735. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4736. my $state = "Database restore $cause";
  4737. $dbh->disconnect() if(defined($dbh));
  4738. ReadingsSingleUpdateValue ($hash, "state", $state, 1);
  4739. Log3 ($name, 3, "DbRep $name - Database restore aborted by \"$cause\" ");
  4740. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4741. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4742. return;
  4743. }
  4744. ####################################################################################################
  4745. # Abbruchroutine Timeout DB-Abfrage
  4746. ####################################################################################################
  4747. sub ParseAborted(@) {
  4748. my ($hash,$cause) = @_;
  4749. my $name = $hash->{NAME};
  4750. my $dbh = $hash->{DBH};
  4751. $cause = $cause?$cause:"Timeout: process terminated";
  4752. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} $cause");
  4753. $dbh->disconnect() if(defined($dbh));
  4754. ReadingsSingleUpdateValue ($hash,"state",$cause, 1);
  4755. delete($hash->{HELPER}{RUNNING_PID});
  4756. return;
  4757. }
  4758. ####################################################################################################
  4759. # Abbruchroutine Timeout DB-Dump
  4760. ####################################################################################################
  4761. sub DumpAborted(@) {
  4762. my ($hash,$cause) = @_;
  4763. my $name = $hash->{NAME};
  4764. my $dbh = $hash->{DBH} if ($hash->{DBH});
  4765. my ($err,$erread);
  4766. $cause = $cause?$cause:"Timeout: process terminated";
  4767. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4768. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4769. # Befehl nach Dump ausführen
  4770. my $ead = AttrVal($name, "executeAfterDump", undef);
  4771. if($ead) {
  4772. Log3 ($name, 4, "DbRep $name - execute command after dump: '$ead' ");
  4773. $err = AnalyzeCommandChain(undef, $ead);
  4774. if ($err) {
  4775. Log3 ($name, 2, "DbRep $name - $err");
  4776. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4777. $erread = "Warning - Database backup ended with \"$cause\" and command after dump not successful";
  4778. }
  4779. }
  4780. my $state = $erread?$erread:$cause;
  4781. $dbh->disconnect() if(defined($dbh));
  4782. ReadingsSingleUpdateValue ($hash, "state", $state, 1);
  4783. Log3 ($name, 2, "DbRep $name - Database dump aborted by \"$cause\" ");
  4784. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  4785. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  4786. return;
  4787. }
  4788. ####################################################################################################
  4789. # Abbruchroutine Timeout DB-Abfrage
  4790. ####################################################################################################
  4791. sub OptimizeAborted(@) {
  4792. my ($hash,$cause) = @_;
  4793. my $name = $hash->{NAME};
  4794. my $dbh = $hash->{DBH};
  4795. $cause = $cause?$cause:"Timeout: process terminated";
  4796. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_OPTIMIZE}}{fn} $cause");
  4797. $dbh->disconnect() if(defined($dbh));
  4798. ReadingsSingleUpdateValue ($hash, "state", $cause, 1);
  4799. delete($hash->{HELPER}{RUNNING_OPTIMIZE});
  4800. return;
  4801. }
  4802. ####################################################################################################
  4803. # SQL-Statement zusammenstellen für DB-Abfrage
  4804. ####################################################################################################
  4805. sub createSelectSql($$$$$$$$) {
  4806. my ($hash,$table,$selspec,$device,$reading,$tf,$tn,$addon) = @_;
  4807. my $name = $hash->{NAME};
  4808. my $dbmodel = $hash->{dbloghash}{MODEL};
  4809. my ($sql,$devs,$danz,$ranz);
  4810. ($devs,$danz,$reading,$ranz) = specsForSql($hash,$device,$reading);
  4811. $sql = "SELECT $selspec FROM $table where ";
  4812. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  4813. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  4814. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  4815. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  4816. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  4817. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  4818. if (($tf && $tn)) {
  4819. $sql .= "TIMESTAMP >= $tf AND TIMESTAMP < $tn ";
  4820. } else {
  4821. if ($dbmodel eq "POSTGRESQL") {
  4822. $sql .= "true ";
  4823. } else {
  4824. $sql .= "1 ";
  4825. }
  4826. }
  4827. $sql .= "$addon;";
  4828. return $sql;
  4829. }
  4830. ####################################################################################################
  4831. # SQL-Statement zusammenstellen für Löschvorgänge
  4832. ####################################################################################################
  4833. sub createDeleteSql($$$$$$$) {
  4834. my ($hash,$table,$device,$reading,$tf,$tn,$addon) = @_;
  4835. my $name = $hash->{NAME};
  4836. my $dbmodel = $hash->{dbloghash}{MODEL};
  4837. my ($sql,$devs,$danz,$ranz);
  4838. if($table eq "current") {
  4839. $sql = "delete FROM $table; ";
  4840. return $sql;
  4841. }
  4842. ($devs,$danz,$reading,$ranz) = specsForSql($hash,$device,$reading);
  4843. $sql = "delete FROM $table where ";
  4844. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  4845. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  4846. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  4847. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  4848. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  4849. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  4850. if ($tf && $tn) {
  4851. $sql .= "TIMESTAMP >= '$tf' AND TIMESTAMP < '$tn' $addon;";
  4852. } else {
  4853. if ($dbmodel eq "POSTGRESQL") {
  4854. $sql .= "true;";
  4855. } else {
  4856. $sql .= "1;";
  4857. }
  4858. }
  4859. return $sql;
  4860. }
  4861. ####################################################################################################
  4862. # Ableiten von Device, Reading-Spezifikationen
  4863. ####################################################################################################
  4864. sub specsForSql($$$) {
  4865. my ($hash,$device,$reading) = @_;
  4866. my $name = $hash->{NAME};
  4867. my @dvspcs = devspec2array($device);
  4868. my $devs = join(",",@dvspcs);
  4869. my $danz = $#dvspcs+1;
  4870. if ($danz > 1) {
  4871. $devs =~ s/,/','/g;
  4872. $devs = "'".$devs."'";
  4873. }
  4874. Log3 $name, 5, "DbRep $name - Device specifications use for select: $devs";
  4875. my @reads = split(/,|\s/,$reading);
  4876. my $ranz = $#reads+1;
  4877. if ($ranz > 1) {
  4878. $reading =~ s/,/','/g;
  4879. $reading = "'".$reading."'";
  4880. }
  4881. Log3 $name, 5, "DbRep $name - Reading specification use for select: $reading";
  4882. return ($devs,$danz,$reading,$ranz);
  4883. }
  4884. ####################################################################################################
  4885. # Check ob Zeitgrenzen bzw. Aggregation gesetzt sind
  4886. # Return "1" wenn Bedingung erfüllt, sonst "0"
  4887. ####################################################################################################
  4888. sub checktimeaggr ($) {
  4889. my ($hash) = @_;
  4890. my $name = $hash->{NAME};
  4891. my $IsTimeSet = 0;
  4892. my $IsAggrSet = 0;
  4893. if (AttrVal($name,"timestamp_begin",undef) || AttrVal($name,"timestamp_end",undef) ||
  4894. AttrVal($name,"timeDiffToNow",undef) || AttrVal($name,"timeOlderThan",undef) ) {
  4895. $IsTimeSet = 1;
  4896. }
  4897. if (AttrVal($name,"aggregation","no") ne "no") {
  4898. $IsAggrSet = 1;
  4899. }
  4900. return ($IsTimeSet,$IsAggrSet);
  4901. }
  4902. ####################################################################################################
  4903. # ReadingsSingleUpdate für Reading, Value, Event
  4904. ####################################################################################################
  4905. sub ReadingsSingleUpdateValue ($$$$) {
  4906. my ($hash,$reading,$val,$ev) = @_;
  4907. my $name = $hash->{NAME};
  4908. readingsSingleUpdate($hash, $reading, $val, $ev);
  4909. userexit($name, $reading, $val);
  4910. return;
  4911. }
  4912. ####################################################################################################
  4913. # Readingsbulkupdate für Reading, Value
  4914. # readingsBeginUpdate und readingsEndUpdate muss vor/nach Funktionsaufruf gesetzt werden
  4915. ####################################################################################################
  4916. sub ReadingsBulkUpdateValue ($$$) {
  4917. my ($hash,$reading,$val) = @_;
  4918. my $name = $hash->{NAME};
  4919. readingsBulkUpdate($hash, $reading, $val);
  4920. userexit($name, $reading, $val);
  4921. return;
  4922. }
  4923. ####################################################################################################
  4924. # Readingsbulkupdate für processing_time, state
  4925. # readingsBeginUpdate und readingsEndUpdate muss vor/nach Funktionsaufruf gesetzt werden
  4926. ####################################################################################################
  4927. sub ReadingsBulkUpdateTimeState ($$$$) {
  4928. my ($hash,$brt,$rt,$sval) = @_;
  4929. my $name = $hash->{NAME};
  4930. if(AttrVal($name, "showproctime", undef)) {
  4931. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt)) if(defined($brt));
  4932. userexit($name, "background_processing_time", sprintf("%.4f",$brt)) if(defined($brt));
  4933. readingsBulkUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt)) if(defined($rt));
  4934. userexit($name, "sql_processing_time", sprintf("%.4f",$rt)) if(defined($rt));
  4935. }
  4936. readingsBulkUpdate($hash, "state", $sval);
  4937. userexit($name, "state", $sval);
  4938. return;
  4939. }
  4940. ####################################################################################################
  4941. # userexit - Funktion um userspezifische Programmaufrufe nach Aktualisierung eines Readings
  4942. # zu ermöglichen, arbeitet OHNE Event abhängig vom Attr userExitFn
  4943. #
  4944. # Aufruf der <UserExitFn> mit $name,$reading,$value
  4945. ####################################################################################################
  4946. sub userexit ($$$) {
  4947. my ($name,$reading,$value) = @_;
  4948. my $hash = $defs{$name};
  4949. return if(!$hash->{HELPER}{USEREXITFN});
  4950. if(!defined($reading)) {$reading = "";}
  4951. if(!defined($value)) {$value = "";}
  4952. $value =~ s/\\/\\\\/g; # escapen of chars for evaluation
  4953. $value =~ s/'/\\'/g;
  4954. my $re = $hash->{HELPER}{UEFN_REGEXP}?$hash->{HELPER}{UEFN_REGEXP}:".*:.*";
  4955. if("$reading:$value" =~ m/^$re$/ ) {
  4956. my @res;
  4957. my $cmd = $hash->{HELPER}{USEREXITFN}."('$name','$reading','$value')";
  4958. $cmd = "{".$cmd."}";
  4959. my $r = AnalyzeCommandChain(undef, $cmd);
  4960. }
  4961. return;
  4962. }
  4963. ####################################################################################################
  4964. # delete Readings before new operation
  4965. ####################################################################################################
  4966. sub delread($) {
  4967. # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  4968. my ($hash) = @_;
  4969. my $name = $hash->{NAME};
  4970. my @allrds = keys%{$defs{$name}{READINGS}};
  4971. my @rdpfdel = split(",", $hash->{HELPER}{RDPFDEL}) if($hash->{HELPER}{RDPFDEL});
  4972. if (@rdpfdel) {
  4973. foreach my $key(@allrds) {
  4974. # Log3 ($name, 1, "DbRep $name - Reading Schlüssel: $key");
  4975. my $dodel = 1;
  4976. foreach my $rdpfdel(@rdpfdel) {
  4977. if($key =~ /$rdpfdel/ || $key eq "state") {
  4978. $dodel = 0;
  4979. }
  4980. }
  4981. if($dodel) {
  4982. delete($defs{$name}{READINGS}{$key});
  4983. }
  4984. }
  4985. } else {
  4986. foreach my $key(@allrds) {
  4987. # Log3 ($name, 1, "DbRep $name - Reading Schlüssel: $key");
  4988. delete($defs{$name}{READINGS}{$key}) if($key ne "state");
  4989. }
  4990. }
  4991. return undef;
  4992. }
  4993. ####################################################################################################
  4994. # erstellen neues SQL-File für Dumproutine
  4995. ####################################################################################################
  4996. sub NewDumpFilename {
  4997. my ($sql_text,$dump_path,$dbname,$time_stamp,$character_set) = @_;
  4998. my $part = "";
  4999. my $sql_file = $dump_path.$dbname."_".$time_stamp.$part.".sql";
  5000. my $backupfile = $dbname."_".$time_stamp.$part.".sql";
  5001. $sql_text .= "/*!40101 SET NAMES '".$character_set."' */;\n";
  5002. $sql_text .= "SET FOREIGN_KEY_CHECKS=0;\n";
  5003. my ($filesize,$err) = WriteToDumpFile($sql_text,$sql_file);
  5004. if($err) {
  5005. return (undef,undef,undef,undef,$err);
  5006. }
  5007. chmod(0777,$sql_file);
  5008. $sql_text = "";
  5009. my $first_insert = 0;
  5010. return ($sql_text,$first_insert,$sql_file,$backupfile,undef);
  5011. }
  5012. ####################################################################################################
  5013. # Schreiben DB-Dumps in SQL-File
  5014. ####################################################################################################
  5015. sub WriteToDumpFile {
  5016. my ($inh,$sql_file) = @_;
  5017. my $filesize;
  5018. my $err = 0;
  5019. if(length($inh) > 0) {
  5020. unless(open(DATEI,">>$sql_file")) {
  5021. $err = "Can't open file '$sql_file' for write access";
  5022. return (undef,$err);
  5023. }
  5024. print DATEI $inh;
  5025. close(DATEI);
  5026. $filesize = (stat($sql_file))[7];
  5027. }
  5028. return ($filesize,undef);
  5029. }
  5030. ####################################################################################################
  5031. # Filesize (Byte) umwandeln in KB bzw. MB
  5032. ####################################################################################################
  5033. sub byte_output {
  5034. my $bytes = shift;
  5035. return if(!defined($bytes));
  5036. return $bytes if(!looks_like_number($bytes));
  5037. my $suffix = "Bytes";
  5038. if ($bytes >= 1024) { $suffix = "KB"; $bytes = sprintf("%.2f",($bytes/1024));};
  5039. if ($bytes >= 1024) { $suffix = "MB"; $bytes = sprintf("%.2f",($bytes/1024));};
  5040. my $ret = sprintf "%.2f",$bytes;
  5041. $ret.=' '.$suffix;
  5042. return $ret;
  5043. }
  5044. ####################################################################################################
  5045. # Tabellenoptimierung MySQL
  5046. ####################################################################################################
  5047. sub mysql_optimize_tables {
  5048. my ($hash,$dbh,@tablenames) = @_;
  5049. my $name = $hash->{NAME};
  5050. my $dbname = $hash->{DATABASE};
  5051. my $ret = 0;
  5052. my $opttbl = 0;
  5053. my $db_tables = $hash->{HELPER}{DBTABLES};
  5054. my ($engine,$tablename,$query,$sth,$value,$db_MB_start,$db_MB_end);
  5055. # Anfangsgröße ermitteln
  5056. $query = "SELECT sum( data_length + index_length ) / 1024 / 1024 FROM information_schema.TABLES where table_schema='$dbname' ";
  5057. Log3 ($name, 5, "DbRep $name - current query: $query ");
  5058. eval { $sth = $dbh->prepare($query);
  5059. $sth->execute;
  5060. };
  5061. if ($@) {
  5062. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  5063. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  5064. $sth->finish;
  5065. $dbh->disconnect;
  5066. return ($@,undef,undef);
  5067. }
  5068. $value = $sth->fetchrow();
  5069. $db_MB_start = sprintf("%.2f",$value);
  5070. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  5071. Log3($name, 3, "DbRep $name - Optimizing tables");
  5072. foreach $tablename (@tablenames) {
  5073. #optimize table if engine supports optimization
  5074. $engine = '';
  5075. $engine = uc($db_tables->{$tablename}{Engine}) if($db_tables->{$tablename}{Engine});
  5076. if ($engine =~ /(MYISAM|BDB|INNODB|ARIA)/) {
  5077. Log3($name, 3, "DbRep $name - Optimizing table `$tablename` ($engine). It will take a while.");
  5078. my $sth_to = $dbh->prepare("OPTIMIZE TABLE `$tablename`");
  5079. $ret = $sth_to->execute;
  5080. if ($ret) {
  5081. Log3($name, 3, "DbRep $name - Table ".($opttbl+1)." `$tablename` optimized successfully.");
  5082. $opttbl++;
  5083. } else {
  5084. Log3($name, 2, "DbRep $name - Error while optimizing table $tablename. Continue with next table or backup.");
  5085. }
  5086. }
  5087. }
  5088. Log3($name, 3, "DbRep $name - $opttbl tables have been optimized.") if($opttbl > 0);
  5089. # Endgröße ermitteln
  5090. eval { $sth->execute; };
  5091. if ($@) {
  5092. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  5093. Log3 ($name, 4, "DbRep $name -> BlockingCall DbRep_optimizeTables finished");
  5094. $sth->finish;
  5095. $dbh->disconnect;
  5096. return ($@,undef,undef);
  5097. }
  5098. $value = $sth->fetchrow();
  5099. $db_MB_end = sprintf("%.2f",$value);
  5100. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  5101. $sth->finish;
  5102. return (undef,$db_MB_start,$db_MB_end);
  5103. }
  5104. ####################################################################################################
  5105. # Dump-Files im dumpDirLocal löschen bis auf die letzten "n"
  5106. ####################################################################################################
  5107. sub deldumpfiles ($$) {
  5108. my ($hash,$bfile) = @_;
  5109. my $name = $hash->{NAME};
  5110. my $dbloghash = $hash->{dbloghash};
  5111. my $dump_path_def = $attr{global}{modpath}."/log/";
  5112. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  5113. my $dfk = AttrVal($name,"dumpFilesKeep", 3);
  5114. my $pfix = (split '\.', $bfile)[ -1 ];
  5115. my $dbname = $hash->{DATABASE};
  5116. my $file = $dbname."_.*".$pfix;
  5117. my @fd;
  5118. if(!opendir(DH, $dump_path_loc)) {
  5119. push(@fd, "No files deleted - Can't open path '$dump_path_loc'");
  5120. return @fd;
  5121. }
  5122. my @files = sort grep {/^$file$/} readdir(DH);
  5123. @files = sort { (stat("$dump_path_loc/$a"))[9] cmp (stat("$dump_path_loc/$b"))[9] } @files
  5124. if(AttrVal("global", "archivesort", "alphanum") eq "timestamp");
  5125. closedir(DH);
  5126. Log3($name, 5, "DbRep $name - Dump files have been found in dumpDirLocal '$dump_path_loc': ".join(', ',@files) );
  5127. my $max = int(@files)-$dfk;
  5128. for(my $i = 0; $i < $max; $i++) {
  5129. push(@fd, $files[$i]);
  5130. Log3($name, 3, "DbRep $name - Deleting old dumpfile '$files[$i]' ");
  5131. unlink("$dump_path_loc/$files[$i]");
  5132. }
  5133. return @fd;
  5134. }
  5135. ####################################################################################################
  5136. # erzeugtes Dump-File aus dumpDirLocal zum FTP-Server übertragen
  5137. ####################################################################################################
  5138. sub sendftp ($$) {
  5139. my ($hash,$bfile) = @_;
  5140. my $name = $hash->{NAME};
  5141. my $dbloghash = $hash->{dbloghash};
  5142. my $dump_path_def = $attr{global}{modpath}."/log/";
  5143. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  5144. my $file = (split /[\/]/, $bfile)[-1];
  5145. my $ftpto = AttrVal($name,"ftpTimeout",30);
  5146. my $ftpUse = AttrVal($name,"ftpUse",0);
  5147. my $ftpuseSSL = AttrVal($name,"ftpUseSSL",0);
  5148. my $ftpDir = AttrVal($name,"ftpDir","/");
  5149. my $ftpPort = AttrVal($name,"ftpPort",21);
  5150. my $ftpServer = AttrVal($name,"ftpServer",undef);
  5151. my $ftpUser = AttrVal($name,"ftpUser","anonymous");
  5152. my $ftpPwd = AttrVal($name,"ftpPwd",undef);
  5153. my $ftpPassive = AttrVal($name,"ftpPassive",0);
  5154. my $ftpDebug = AttrVal($name,"ftpDebug",0);
  5155. my ($ftperr,$ftpmsg,$ftp);
  5156. # kein FTP verwenden oder möglich
  5157. return ($ftperr,$ftpmsg) if((!$ftpUse && !$ftpuseSSL) || !$bfile);
  5158. if(!$ftpServer) {
  5159. $ftperr = "FTP-Error: FTP-Server isn't set.";
  5160. Log3($name, 2, "DbRep $name - $ftperr");
  5161. return ($ftperr,undef);
  5162. }
  5163. if(!opendir(DH, $dump_path_loc)) {
  5164. $ftperr = "FTP-Error: Can't open path '$dump_path_loc'";
  5165. Log3($name, 2, "DbRep $name - $ftperr");
  5166. return ($ftperr,undef);
  5167. }
  5168. my $mod_ftpssl = 0;
  5169. my $mod_ftp = 0;
  5170. my $mod;
  5171. if ($ftpuseSSL) {
  5172. # FTP mit SSL soll genutzt werden
  5173. $mod = "Net::FTPSSL => e.g. with 'sudo cpan -i Net::FTPSSL' ";
  5174. eval { require Net::FTPSSL; };
  5175. if(!$@){
  5176. $mod_ftpssl = 1;
  5177. import Net::FTPSSL;
  5178. }
  5179. } else {
  5180. # nur FTP
  5181. $mod = "Net::FTP";
  5182. eval { require Net::FTP; };
  5183. if(!$@){
  5184. $mod_ftp = 1;
  5185. import Net::FTP;
  5186. }
  5187. }
  5188. if ($ftpuseSSL && $mod_ftpssl) {
  5189. # use ftp-ssl
  5190. my $enc = "E";
  5191. eval { $ftp = Net::FTPSSL->new($ftpServer, Port => $ftpPort, Timeout => $ftpto, Debug => $ftpDebug, Encryption => $enc) }
  5192. or $ftperr = "FTP-SSL-ERROR: Can't connect - $@";
  5193. } elsif (!$ftpuseSSL && $mod_ftp) {
  5194. # use plain ftp
  5195. eval { $ftp = Net::FTP->new($ftpServer, Port => $ftpPort, Timeout => $ftpto, Debug => $ftpDebug, Passive => $ftpPassive) }
  5196. or $ftperr = "FTP-Error: Can't connect - $@";
  5197. } else {
  5198. $ftperr = "FTP-Error: required module couldn't be loaded. You have to install it first: $mod.";
  5199. }
  5200. if ($ftperr) {
  5201. Log3($name, 2, "DbRep $name - $ftperr");
  5202. return ($ftperr,undef);
  5203. }
  5204. my $pwdstr = $ftpPwd?$ftpPwd:" ";
  5205. $ftp->login($ftpUser, $ftpPwd) or $ftperr = "FTP-Error: Couldn't login with user '$ftpUser' and password '$pwdstr' ";
  5206. if ($ftperr) {
  5207. Log3($name, 2, "DbRep $name - $ftperr");
  5208. return ($ftperr,undef);
  5209. }
  5210. $ftp->binary();
  5211. # FTP Verzeichnis setzen
  5212. $ftp->cwd($ftpDir) or $ftperr = "FTP-Error: Couldn't change directory to '$ftpDir' ";
  5213. if ($ftperr) {
  5214. Log3($name, 2, "DbRep $name - $ftperr");
  5215. return ($ftperr,undef);
  5216. }
  5217. $dump_path_loc =~ s/(\/$|\\$)//;
  5218. Log3($name, 3, "DbRep $name - FTP: transferring ".$dump_path_loc."/".$file);
  5219. $ftpmsg = $ftp->put($dump_path_loc."/".$file);
  5220. if (!$ftpmsg) {
  5221. $ftperr = "FTP-Error: Couldn't transfer ".$file." to ".$ftpServer." into dir ".$ftpDir;
  5222. Log3($name, 2, "DbRep $name - $ftperr");
  5223. } else {
  5224. $ftpmsg = "FTP: ".$file." transferred successfully to ".$ftpServer." into dir ".$ftpDir;
  5225. Log3($name, 3, "DbRep $name - $ftpmsg");
  5226. }
  5227. return ($ftperr,$ftpmsg);
  5228. }
  5229. ####################################################################################################
  5230. # Browser Refresh nach DB-Abfrage
  5231. ####################################################################################################
  5232. sub browser_refresh($) {
  5233. my ($hash) = @_;
  5234. RemoveInternalTimer($hash, "browser_refresh");
  5235. {FW_directNotify("#FHEMWEB:WEB", "location.reload('true')", "")};
  5236. # map { FW_directNotify("#FHEMWEB:$_", "location.reload(true)", "") } devspec2array("WEB.*");
  5237. return;
  5238. }
  5239. ####################################################################################################
  5240. # Test auf Daylight saving time
  5241. ####################################################################################################
  5242. sub dsttest ($$$) {
  5243. my ($hash,$runtime,$aggsec) = @_;
  5244. my $name = $hash->{NAME};
  5245. my $dstchange = 0;
  5246. # der Wechsel der daylight saving time wird dadurch getestet, dass geprüft wird
  5247. # ob im Vergleich der aktuellen zur nächsten Selektionsperiode von "$aggsec (day, week, month)"
  5248. # ein Wechsel der daylight saving time vorliegt
  5249. my $dst = (localtime($runtime))[8]; # ermitteln daylight saving aktuelle runtime
  5250. my $time_str = localtime($runtime+$aggsec); # textual time representation
  5251. my $dst_new = (localtime($runtime+$aggsec))[8]; # ermitteln daylight saving nächste runtime
  5252. if ($dst != $dst_new) {
  5253. $dstchange = 1;
  5254. }
  5255. Log3 ($name, 5, "DbRep $name - Daylight savings changed: $dstchange (on $time_str)");
  5256. return $dstchange;
  5257. }
  5258. ####################################################################################################
  5259. # Counthash Untersuchung
  5260. # Logausgabe der Anzahl verarbeiteter Datensätze pro Zeitraum / Aggregation
  5261. # Rückgabe eines ncp-hash (no calc in period) mit den Perioden für die keine Differenz berechnet
  5262. # werden konnte weil nur ein Datensatz in der Periode zur Verfügung stand
  5263. ####################################################################################################
  5264. sub calcount ($$) {
  5265. my ($hash,$ch) = @_;
  5266. my $name = $hash->{NAME};
  5267. my %ncp = ();
  5268. Log3 ($name, 4, "DbRep $name - count of values used for calc:");
  5269. foreach my $key (sort(keys%{$ch})) {
  5270. Log3 ($name, 4, "$key => ". $ch->{$key});
  5271. if($ch->{$key} eq "1") {
  5272. $ncp{"$key"} = " ||";
  5273. }
  5274. }
  5275. return \%ncp;
  5276. }
  5277. ################################################################
  5278. # check ob primary key genutzt wird
  5279. ################################################################
  5280. sub DbRep_checkUsePK ($$){
  5281. my ($hash,$dbh) = @_;
  5282. my $name = $hash->{NAME};
  5283. my $dbconn = $hash->{dbloghash}{dbconn};
  5284. my $upkh = 0;
  5285. my $upkc = 0;
  5286. my (@pkh,@pkc);
  5287. my $db = (split("=",(split(";",$dbconn))[0]))[1];
  5288. eval {@pkh = $dbh->primary_key( undef, undef, 'history' );};
  5289. eval {@pkc = $dbh->primary_key( undef, undef, 'current' );};
  5290. my $pkh = (!@pkh || @pkh eq "")?"none":join(",",@pkh);
  5291. my $pkc = (!@pkc || @pkc eq "")?"none":join(",",@pkc);
  5292. $pkh =~ tr/"//d;
  5293. $pkc =~ tr/"//d;
  5294. $upkh = 1 if(@pkh && @pkh ne "none");
  5295. $upkc = 1 if(@pkc && @pkc ne "none");
  5296. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key used in $db.history: $pkh";
  5297. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key used in $db.current: $pkc";
  5298. return ($upkh,$upkc,$pkh,$pkc);
  5299. }
  5300. ####################################################################################################
  5301. # Test-Sub zu Testzwecken
  5302. ####################################################################################################
  5303. sub testexit ($) {
  5304. my ($hash) = @_;
  5305. my $name = $hash->{NAME};
  5306. if ( !DbRep_Connect($hash) ) {
  5307. Log3 ($name, 2, "DbRep $name - DB connect failed. Database down ? ");
  5308. ReadingsSingleUpdateValue ($hash, "state", "disconnected", 1);
  5309. return;
  5310. } else {
  5311. my $dbh = $hash->{DBH};
  5312. Log3 ($name, 3, "DbRep $name - --------------- FILE INFO --------------");
  5313. my $sqlfile = $dbh->sqlite_db_filename();
  5314. Log3 ($name, 3, "DbRep $name - FILE : $sqlfile ");
  5315. # # $dbh->table_info( $catalog, $schema, $table)
  5316. # my $sth = $dbh->table_info('', '%', '%');
  5317. # my $tables = $dbh->selectcol_arrayref($sth, {Columns => [3]});
  5318. # my $table = join ', ', @$tables;
  5319. # Log3 ($name, 3, "DbRep $name - SQL_TABLES : $table");
  5320. Log3 ($name, 3, "DbRep $name - --------------- PRAGMA --------------");
  5321. my @InfoTypes = ('sqlite_db_status');
  5322. foreach my $row (@InfoTypes) {
  5323. # my @linehash = $dbh->$row;
  5324. my $array= $dbh->$row ;
  5325. # push(@row_array, @array);
  5326. while ((my $key, my $val) = each %{$array}) {
  5327. Log3 ($name, 3, "DbRep $name - PRAGMA : $key : ".%{$val});
  5328. }
  5329. }
  5330. # $sth->finish;
  5331. $dbh->disconnect;
  5332. }
  5333. return;
  5334. }
  5335. 1;
  5336. =pod
  5337. =item helper
  5338. =item summary Reporting & Management content of DbLog-DB's. Content is depicted as readings
  5339. =item summary_DE Reporting & Management von DbLog-DB Content. Darstellung als Readings
  5340. =begin html
  5341. <a name="DbRep"></a>
  5342. <h3>DbRep</h3>
  5343. <ul>
  5344. <br>
  5345. The purpose of this module is browsing and managing the content of DbLog-databases. The searchresults can be evaluated concerning to various aggregations and the appropriate
  5346. Readings will be filled. The data selection will been done by declaration of device, reading and the time settings of selection-begin and selection-end. <br><br>
  5347. All database operations are implemented nonblocking. Optional the execution time of SQL-statements in background can also be determined and provided as reading.
  5348. (refer to <a href="#DbRepattr">attributes</a>). <br>
  5349. All existing readings will be deleted when a new operation starts. By attribute "readingPreventFromDel" a comma separated list of readings which are should prevent
  5350. from deletion can be provided. <br><br>
  5351. Currently the following functions are provided: <br><br>
  5352. <ul><ul>
  5353. <li> Selection of all datasets within adjustable time limits. </li>
  5354. <li> Exposure of datasets of a Device/Reading-combination within adjustable time limits. </li>
  5355. <li> Selecion of datasets by usage of dynamically calclated time limits at execution time. </li>
  5356. <li> Calculation of quantity of datasets of a Device/Reading-combination within adjustable time limits and several aggregations. </li>
  5357. <li> The calculation of summary- , difference- , maximum- , minimum- and averageValues of numeric readings within adjustable time limits and several aggregations. </li>
  5358. <li> The deletion of datasets. The containment of deletion can be done by Device and/or Reading as well as fix or dynamically calculated time limits at execution time. </li>
  5359. <li> export of datasets to file (CSV-format). </li>
  5360. <li> import of datasets from file (CSV-Format). </li>
  5361. <li> rename of device names in datasets </li>
  5362. <li> automatic rename of device names in datasets and other DbRep-definitions after FHEM "rename" command (see <a href="#DbRepAutoRename">DbRep-Agent</a>) </li>
  5363. <li> Execution of arbitrary user specific SQL-commands </li>
  5364. <li> creation of backups non-blocking (MySQL) </li>
  5365. <li> transfer dumpfiles to a FTP server after backup </li>
  5366. <li> restore of serverSide-backups non-blocking (MySQL) </li>
  5367. <li> optimize the connected database (optimizeTables, vacuum) </li>
  5368. <li> report of existing database processes (MySQL) </li>
  5369. <li> purge content of current-table </li>
  5370. <li> fill up the current-table with a (tunable) extract of the history-table</li>
  5371. </ul></ul>
  5372. <br>
  5373. To activate the function "Autorename" the attribute "role" has to be assigned to a defined DbRep-device. The standard role after DbRep definition is "Client.
  5374. Please read more in section <a href="#DbRepAutoRename">DbRep-Agent</a> . <br><br>
  5375. DbRep provides a UserExit function. By that interface the user can execute own program code dependent from free
  5376. definable Reading/Value-combinations (Regex). The interface works without respectively independent from event
  5377. generation.
  5378. Further informations you can find as described at <a href="#DbRepattr">attribute</a> "userExitFn". <br><br>
  5379. FHEM-Forum: <br>
  5380. <a href="https://forum.fhem.de/index.php/topic,53584.msg452567.html#msg452567">Modul 93_DbRep - Reporting and Management of database content (DbLog)</a>.<br><br>
  5381. <br>
  5382. <b>Preparations </b> <br><br>
  5383. The module requires the usage of a DbLog instance and the credentials of the database definition will be used. <br>
  5384. Only the content of table "history" will be included if isn't other is explained. <br><br>
  5385. Overview which other Perl-modules DbRep is using: <br><br>
  5386. Net::FTP (only if FTP-Transfer after database dump is used) <br>
  5387. Net::FTPSSL (only if FTP-Transfer with encoding after database dump is used) <br>
  5388. POSIX <br>
  5389. Time::HiRes <br>
  5390. Time::Local <br>
  5391. Scalar::Util <br>
  5392. DBI <br>
  5393. Blocking (FHEM-module) <br><br>
  5394. Due to performance reason the following index should be created in addition: <br>
  5395. <code>
  5396. CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;
  5397. </code>
  5398. </ul>
  5399. <br>
  5400. <a name="DbRepdefine"></a>
  5401. <b>Definition</b>
  5402. <br>
  5403. <ul>
  5404. <code>
  5405. define &lt;name&gt; DbRep &lt;name of DbLog-instance&gt;
  5406. </code>
  5407. <br><br>
  5408. (&lt;name of DbLog-instance&gt; - name of the database instance which is wanted to analyze needs to be inserted)
  5409. </ul>
  5410. <br><br>
  5411. <a name="DbRepset"></a>
  5412. <b>Set </b>
  5413. <ul>
  5414. Currently following set-commands are included. They are used to trigger the evaluations and define the evaluation option option itself.
  5415. The criteria of searching database content and determine aggregation is carried out by setting several <a href="#DbRepattr">attributes</a>.
  5416. <br><br>
  5417. <ul><ul>
  5418. <li><b> averageValue </b> - calculates the average value of readingvalues DB-column "VALUE") between period given by timestamp-<a href="#DbRepattr">attributes</a> which are set.
  5419. The reading to evaluate must be defined using attribute "reading". </li> <br>
  5420. <li><b> countEntries [history|current] </b> - provides the number of table-entries (default: history) between period set
  5421. by timestamp-<a href="#DbRepattr">attributes</a> if set.
  5422. If timestamp-attributes are not set, all entries of the table will be count.
  5423. The <a href="#DbRepattr">attributes</a> "device" and "reading" can be used to
  5424. limit the evaluation. </li> <br>
  5425. <li><b> delEntries </b> - deletes all database entries or only the database entries specified by <a href="#DbRepattr">attributes</a> Device and/or
  5426. Reading and the entered time period between "timestamp_begin", "timestamp_end" (if set) or "timeDiffToNow/timeOlderThan". <br><br>
  5427. <ul>
  5428. "timestamp_begin" is set: deletes db entries <b>from</b> this timestamp until current date/time <br>
  5429. "timestamp_end" is set : deletes db entries <b>until</b> this timestamp <br>
  5430. both Timestamps are set : deletes db entries <b>between</b> these timestamps <br><br>
  5431. Due to security reasons the attribute "allowDeletion" needs to be set to unlock the delete-function. <br>
  5432. </li>
  5433. <br>
  5434. </ul>
  5435. <li><b> deviceRename </b> - renames the device name of a device inside the connected database (Internal DATABASE).
  5436. The devicename will allways be changed in the <b>entire</b> database. Possibly set time limits or restrictions by
  5437. <a href="#DbRepattr">attributes</a> device and/or reading will not be considered. <br><br>
  5438. <ul>
  5439. <b>input format: </b> set &lt;name&gt; deviceRename &lt;old device name&gt;,&lt;new device name&gt; <br>
  5440. # The amount of renamed device names (datasets) will be displayed in reading "device_renamed". <br>
  5441. # If the device name to be renamed was not found in the database, a WARNUNG will appear in reading "device_not_renamed". <br>
  5442. # Appropriate entries will be written to Logfile if verbose >= 3 is set.
  5443. <br><br>
  5444. </li> <br>
  5445. </ul>
  5446. <li><b> diffValue </b> - calculates the defference of the readingvalues DB-column "VALUE") between period given by <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  5447. The reading to evaluate must be defined using attribute "reading".
  5448. This function is mostly reasonable if readingvalues are increasing permanently and don't write value-differences to the database.
  5449. The difference will be generated from the first available dataset (VALUE-Field) to the last available dataset between the
  5450. specified time linits/aggregation, in which a balanced difference value of the previous aggregation period will be transfered to the
  5451. following aggregation period in case this period contains a value. <br>
  5452. An possible counter overrun (restart with value "0") will be considered (compare <a href="#DbRepattr">attribute</a> "diffAccept"). <br><br>
  5453. If only one dataset will be found within the evalution period, the difference can be calculated only in combination with the balanced
  5454. difference of the previous aggregation period. In this case a logical inaccuracy according the assignment of the difference to the particular aggregation period
  5455. can be possible. Hence in warning in "state" will be placed and the reading "less_data_in_period" with a list of periods
  5456. with only one dataset found in it will be created.
  5457. <br><br>
  5458. <ul>
  5459. <b>Note: </b><br>
  5460. Within the evaluation respectively aggregation period (day, week, month, etc.) you should make available at least one dataset
  5461. at the beginning and one dataset at the end of each aggregation period to take the difference calculation as much as possible.
  5462. <br>
  5463. <br>
  5464. </li>
  5465. </ul>
  5466. <li><b> dumpMySQL [clientSide | serverSide]</b>
  5467. - creates a dump of the connected MySQL database. <br>
  5468. Depended from selected option the dump will be created on Client- or on Serv-Side. <br>
  5469. The variants differs each other concerning the executing system, the creating location, the usage of
  5470. attributes, the function result and the needed hardware ressources. <br>
  5471. The option "clientSide" e.g. needs more powerful FHEM-Server hardware, but saves all available
  5472. tables inclusive possibly created views.
  5473. <br><br>
  5474. <ul>
  5475. <b><u>Option clientSide</u></b> <br>
  5476. The dump will be created by client (FHEM-Server) and will be saved in FHEM log-directory by
  5477. default.
  5478. The target directory can be set by <a href="#DbRepattr">attribute</a> "dumpDirLocal" and has to be
  5479. writable by the FHEM process. <br>
  5480. Before executing the dump a table optimization can be processed optionally (see attribute
  5481. "optimizeTablesBeforeDump") as well as a FHEM-command (attribute "executeBeforeDump"). <br><br>
  5482. <b>Attention ! <br>
  5483. To avoid FHEM from blocking, you have to operate DbLog in asynchronous mode if the table
  5484. optimization want to be used ! </b> <br><br>
  5485. After the dump a FHEM-command can be executed as well (see attribute "executeAfterDump"). <br>
  5486. By other <a href="#DbRepattr">attributes</a> the run-time behavior of the function can be
  5487. controlled to optimize the performance and demand of ressources. <br><br>
  5488. The attributes relevant for function "dumpMySQL clientSide" are "dumpComment", "dumpDirLocal", "dumpMemlimit",
  5489. "dumpSpeed ", "dumpFilesKeep", "executeBeforeDump", "executeAfterDump" and
  5490. "optimizeTablesBeforeDump". <br>
  5491. After a successfull finished dump old dumpfiles will be deleted and only the number of attribute
  5492. "dumpFilesKeep" (default: 3) would remain in target directory "dumpDirLocal". <br><br>
  5493. The <b>naming convention of dump files</b> is: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sql <br><br>
  5494. The created dumpfile may imported on the MySQL-Server by e.g.: <br><br>
  5495. <ul>
  5496. mysql -u &lt;user&gt; -p &lt;dbname&gt; < &lt;filename&gt;.sql <br><br>
  5497. </ul>
  5498. to restore the database from the dump. <br><br><br>
  5499. <b><u>Option serverSide</u></b> <br>
  5500. The dump will be created on the MySQL-Server and will be saved in its Home-directory
  5501. by default. <br>
  5502. The whole history-table (not the current-table) will be exported <b>CSV-formatted</b> without
  5503. any restrictions. <br>
  5504. Before executing the dump a table optimization can be processed optionally (see attribute
  5505. "optimizeTablesBeforeDump") as well as a FHEM-command (attribute "executeBeforeDump"). <br><br>
  5506. <b>Attention ! <br>
  5507. To avoid FHEM from blocking, you have to operate DbLog in asynchronous mode if the table
  5508. optimization want to be used ! </b> <br><br>
  5509. After the dump a FHEM-command can be executed as well (see attribute "executeAfterDump"). <br>
  5510. The attributes relevant for function "dumpMySQL serverSide" are "dumpDirRemote", "dumpDirLocal",
  5511. "dumpFilesKeep", "optimizeTablesBeforeDump", "executeBeforeDump" and "executeAfterDump". <br><br>
  5512. The target directory can be set by <a href="#DbRepattr">attribute</a> "dumpDirRemote".
  5513. It must be located on the MySQL-Host and has to be writable by the MySQL-server process. <br>
  5514. The used database user must have the "FILE"-privilege. <br><br>
  5515. <b>Note:</b> <br>
  5516. If the internal version management of DbRep should be used and the size of the created dumpfile be
  5517. reported, you have to mount the remote MySQL-Server directory "dumpDirRemote" on the client
  5518. and publish it to the DbRep-device by fill out the <a href="#DbRepattr">attribute</a>
  5519. "dumpDirLocal". <br>
  5520. Same is necessary if ftp transfer after dump is to be used (attribute "ftpUse" respectively "ftpUseSSL").
  5521. <br><br>
  5522. <ul>
  5523. <b>Example: </b> <br>
  5524. attr &lt;DbRep-device&gt; dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM/ <br>
  5525. attr &lt;DbRep-device&gt; dumpDirLocal /sds1/backup/dumps_FHEM/ <br>
  5526. attr &lt;DbRep-device&gt; dumpFilesKeep 2 <br><br>
  5527. # The dump will be created remote on the MySQL-Server in directory
  5528. '/volume1/ApplicationBackup/dumps_FHEM/'. <br>
  5529. # The internal version management searches in local mounted directory '/sds1/backup/dumps_FHEM/'
  5530. for present dumpfiles and deletes these files except the last two versions. <br>
  5531. <br>
  5532. </ul>
  5533. If the internal version management is used, after a successfull finished dump old dumpfiles will
  5534. be deleted and only the number of attribute "dumpFilesKeep" (default: 3) would remain in target
  5535. directory "dumpDirLocal" (the mounted "dumpDirRemote").
  5536. In that case FHEM needs write permissions to the directory "dumpDirLocal". <br><br>
  5537. The <b>naming convention of dump files</b> is: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.csv <br><br>
  5538. You can start a restore of table history from serverSide-Backup by command: <br><br>
  5539. <ul>
  5540. set &lt;name&gt; &lt;restoreMySQL&gt; &lt;filename&gt;.csv <br><br>
  5541. </ul>
  5542. <br><br>
  5543. <b><u>FTP-Transfer after Dump</u></b> <br>
  5544. If those possibility is be used, the <a href="#DbRepattr">attribute</a> "ftpUse" or
  5545. "ftpUseSSL" has to be set. The latter if encoding for FTP is to be used. <br>
  5546. Further <a href="#DbRepattr">attributes</a> are: <br><br>
  5547. <ul>
  5548. <table>
  5549. <colgroup> <col width=5%> <col width=95%> </colgroup>
  5550. <tr><td> ftpUse </td><td>: FTP Transfer after dump will be switched on (without SSL encoding) </td></tr>
  5551. <tr><td> ftpUser </td><td>: User for FTP-server login, default: anonymous </td></tr>
  5552. <tr><td> ftpUseSSL </td><td>: FTP Transfer with SSL encoding after dump </td></tr>
  5553. <tr><td> ftpDebug </td><td>: debugging of FTP communication for diagnostics </td></tr>
  5554. <tr><td> ftpDir </td><td>: directory on FTP-server in which the file will be send into (default: "/") </td></tr>
  5555. <tr><td> ftpPassive </td><td>: set if passive FTP is to be used </td></tr>
  5556. <tr><td> ftpPort </td><td>: FTP-Port, default: 21 </td></tr>
  5557. <tr><td> ftpPwd </td><td>: password of FTP-User, not set by default </td></tr>
  5558. <tr><td> ftpServer </td><td>: name or IP-address of FTP-server. <b>absolutely essential !</b> </td></tr>
  5559. <tr><td> ftpTimeout </td><td>: timeout of FTP-connection in seconds (default: 30). </td></tr>
  5560. </table>
  5561. </ul>
  5562. <br>
  5563. <br>
  5564. </ul>
  5565. </li><br>
  5566. <li><b> exportToFile </b> - exports DB-entries to a file in CSV-format between period given by timestamp.
  5567. Limitations of selections can be set by <a href="#DbRepattr">attributes</a> Device and/or Reading.
  5568. The filename will be defined by <a href="#DbRepattr">attribute</a> "expimpfile" . </li> <br>
  5569. <li><b> fetchrows [history|current] </b> - provides <b>all</b> table-entries (default: history)
  5570. between period given by timestamp-<a href="#DbRepattr">attributes</a>.
  5571. A possibly set aggregation will <b>not</b> be considered. </li> <br>
  5572. <li><b> insert </b> - use it to insert data ito table "history" manually. Input values for Date, Time and Value are mandatory. The database fields for Type and Event will be filled in with "manual" automatically and the values of Device, Reading will be get from set <a href="#DbRepattr">attributes</a>. <br><br>
  5573. <ul>
  5574. <b>input format: </b> Date,Time,Value,[Unit] <br>
  5575. # Unit is optional, attributes of device, reading must be set ! <br>
  5576. # If "Value=0" has to be inserted, use "Value = 0.0" to do it. <br><br>
  5577. <b>example:</b> 2016-08-01,23:00:09,TestValue,TestUnit <br>
  5578. # Spaces are NOT allowed in fieldvalues ! <br>
  5579. <br>
  5580. <b>Note: </b><br>
  5581. Please consider to insert AT LEAST two datasets into the intended time / aggregatiom period (day, week, month, etc.) because of
  5582. it's needed by function diffValue. Otherwise no difference can be calculated and diffValue will be print out "0" for the respective period !
  5583. <br>
  5584. <br>
  5585. </li>
  5586. </ul>
  5587. <li><b> importFromFile </b> - imports datasets in CSV format from file into database. The filename will be set by <a href="#DbRepattr">attribute</a> "expimpfile". <br><br>
  5588. <ul>
  5589. <b>dataset format: </b> "TIMESTAMP","DEVICE","TYPE","EVENT","READING","VALUE","UNIT" <br><br>
  5590. # The fields "TIMESTAMP","DEVICE","TYPE","EVENT","READING" and "VALUE" have to be set. The field "UNIT" is optional.
  5591. The file content will be imported transactional. That means all of the content will be imported or, in case of error, nothing of it.
  5592. If an extensive file will be used, DON'T set verbose = 5 because of a lot of datas would be written to the logfile in this case.
  5593. It could lead to blocking or overload FHEM ! <br><br>
  5594. <b>Example: </b> "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573","" <br>
  5595. <br>
  5596. </li> <br>
  5597. </ul>
  5598. <li><b> maxValue </b> - calculates the maximum value of readingvalues DB-column "VALUE") between period given by <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  5599. The reading to evaluate must be defined using attribute "reading".
  5600. The evaluation contains the timestamp of the <b>last</b> appearing of the identified maximum value within the given period. </li> <br>
  5601. <li><b> minValue </b> - calculates the miniimum value of readingvalues DB-column "VALUE") between period given by <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  5602. The reading to evaluate must be defined using attribute "reading".
  5603. The evaluation contains the timestamp of the <b>first</b> appearing of the identified minimum value within the given period. </li> <br>
  5604. <li><b> optimizeTables </b> - optimize tables in the connected database (MySQL). <br><br>
  5605. <ul>
  5606. <b>Note:</b> <br>
  5607. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  5608. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  5609. </li><br>
  5610. </ul>
  5611. <li><b> readingRename </b> - renames the reading name of a device inside the connected database (see Internal DATABASE).
  5612. The readingname will allways be changed in the <b>entire</b> database. Possibly set time limits or restrictions by
  5613. <a href="#DbRepattr">attributes</a> device and/or reading will not be considered. <br><br>
  5614. <ul>
  5615. <b>input format: </b> set &lt;name&gt; readingRename &lt;old reading name&gt;,&lt;new reading name&gt; <br>
  5616. # The amount of renamed reading names (datasets) will be displayed in reading "reading_renamed". <br>
  5617. # If the reading name to be renamed was not found in the database, a WARNUNG will appear in reading "reading_not_renamed". <br>
  5618. # Appropriate entries will be written to Logfile if verbose >= 3 is set.
  5619. <br><br>
  5620. </li> <br>
  5621. </ul>
  5622. <li><b> restoreMySQL &lt;file&gt;.csv </b> - imports the content of table history from a serverSide-backup. <br>
  5623. The function provides a drop-down-list of files which can be used for restore.
  5624. Therefore you have to mount the remote directory "dumpDirRemote" of the MySQL-Server on the
  5625. Client and make it usable to the DbRep-device by setting the <a href="#DbRepattr">attribute</a>
  5626. "dumpDirLocal". <br>
  5627. All files with extension "csv" and if the filename is beginning with the name of the connected database
  5628. (see Internal DATABASE) will be listed. <br><br>
  5629. </li><br>
  5630. <li><b> sqlCmd </b> - executes an arbitrary user specific command. <br>
  5631. If the command contains a operation to delete data, the <a href="#DbRepattr">attribute</a>
  5632. "allowDeletion" has to be set for security reason. <br>
  5633. The statement doesn't consider limitations by attributes device and/or reading. <br>
  5634. If the <a href="#DbRepattr">attributes</a> "timestamp_begin" respectively "timestamp_end"
  5635. should assumed in the statement, you can use the placeholder "<b>§timestamp_begin§</b>" respectively
  5636. "<b>§timestamp_end§</b>" on suitable place. <br><br>
  5637. <ul>
  5638. <b>Examples of SQL-statements: </b> <br><br>
  5639. <ul>
  5640. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-01-06 00:00:00" group by DEVICE having count(*) > 800 </li>
  5641. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-05-06 00:00:00" group by DEVICE </li>
  5642. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= §timestamp_begin§ group by DEVICE </li>
  5643. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE like "Te%t" order by `TIMESTAMP` desc </li>
  5644. <li>set &lt;name&gt; sqlCmd select * from history where `TIMESTAMP` > "2017-05-09 18:03:00" order by `TIMESTAMP` desc </li>
  5645. <li>set &lt;name&gt; sqlCmd select * from current order by `TIMESTAMP` desc </li>
  5646. <li>set &lt;name&gt; sqlCmd select sum(VALUE) as 'Einspeisung am 04.05.2017', count(*) as 'Anzahl' FROM history where `READING` = "Einspeisung_WirkP_Zaehler_Diff" and TIMESTAMP between '2017-05-04' AND '2017-05-05' </li>
  5647. <li>set &lt;name&gt; sqlCmd delete from current </li>
  5648. <li>set &lt;name&gt; sqlCmd delete from history where TIMESTAMP < "2016-05-06 00:00:00" </li>
  5649. <li>set &lt;name&gt; sqlCmd update history set VALUE='TestVa$$ue$' WHERE VALUE='TestValue' </li>
  5650. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE = "Test" </li>
  5651. <li>set &lt;name&gt; sqlCmd insert into history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES ('2017-05-09 17:00:14','Test','manuell','manuell','Tes§e','TestValue','°C') </li>
  5652. </ul>
  5653. <br>
  5654. The result of the statement will be shown in <a href="#DbRepReadings">Reading</a> "SqlResult".
  5655. By <a href="#DbRepattr">attribut</a> "sqlResultFormat" the fomatting can be choosen. <br><br>
  5656. <b>Note:</b> <br>
  5657. Even though the module works non-blocking regarding to database operations, a huge
  5658. sample space (number of rows/readings) could block the browser session respectively
  5659. FHEMWEB.
  5660. If you are unsure about the result of the statement, you should preventively add a limit to
  5661. the statement. <br><br>
  5662. </li><br>
  5663. </ul>
  5664. <li><b> sumValue </b> - calculates the amount of readingvalues DB-column "VALUE") between period given by
  5665. <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or
  5666. "timeDiffToNow / timeOlderThan". The reading to evaluate must be defined using attribute
  5667. "reading". Using this function is mostly reasonable if value-differences of readings
  5668. are written to the database. </li> <br>
  5669. <li><b> tableCurrentFillup </b> - the current-table will be filled u with an extract of the history-table.
  5670. The <a href="#DbRepattr">attributes</a> for limiting time and device, reading are considered.
  5671. Thereby the content of the extract can be affected. In the associated DbLog-device the attribute "DbLogType" should be set to
  5672. "SampleFill/History". </li> <br>
  5673. <li><b> tableCurrentPurge </b> - deletes the content of current-table. There are no limits, e.g. by attributes "timestamp_begin", "timestamp_end", device, reading
  5674. and so on, considered. </li> <br>
  5675. <li><b> vacuum </b> - optimize tables in the connected database (SQLite, PostgreSQL). <br><br>
  5676. <ul>
  5677. <b>Note:</b> <br>
  5678. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  5679. is operating in asynchronous mode to avoid FHEM from blocking. <br><br>
  5680. </li>
  5681. </ul><br>
  5682. <br>
  5683. </ul></ul>
  5684. <b>For all evaluation variants (except sqlCmd) applies: </b> <br>
  5685. In addition to the needed reading the device can be complemented to restrict the datasets for reporting / function.
  5686. If the time limit attributes are not set, the period from '1970-01-01 01:00:00' to the current date/time will be used as selection criterion.
  5687. <br><br>
  5688. <b>Note: </b> <br>
  5689. If you are in detail view it could be necessary to refresh the browser to see the result of operation as soon in DeviceOverview section "state = done" will be shown.
  5690. <br><br>
  5691. </ul>
  5692. <a name="DbRepget"></a>
  5693. <b>Get </b>
  5694. <ul>
  5695. The get-commands of DbRep provide to retrieve some metadata of the used database instance.
  5696. Those are for example adjusted server parameter, server variables, datadasestatus- and table informations. THe available get-functions depending of
  5697. the used database type. So for SQLite curently only "get svrinfo" is usable. The functions nativ are delivering a lot of outpit values.
  5698. They can be limited by function specific <a href="#DbRepattr">attributes</a>. The filter has to be setup by a comma separated list.
  5699. SQL-Wildcard (%) can be used to setup the list arguments.
  5700. <br><br>
  5701. <b>Note: </b> <br>
  5702. After executing a get-funktion in detail view please make a browser refresh to see the results !
  5703. <br><br>
  5704. <ul><ul>
  5705. <li><b> dbstatus </b> - lists global informations about MySQL server status (e.g. informations related to cache, threads, bufferpools, etc. ).
  5706. Initially all available informations are reported. Using the <a href="#DbRepattr">attribute</a> "showStatus" the quantity of
  5707. results can be limited to show only the desired values. Further detailed informations of items meaning are
  5708. explained <a href=http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html>there</a>. <br>
  5709. <br><ul>
  5710. <b>Example</b> <br>
  5711. get &lt;name&gt; dbstatus <br>
  5712. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  5713. # Only readings containing "uptime" and "qcache" in name will be created
  5714. </li>
  5715. <br><br>
  5716. </ul>
  5717. <li><b> dbvars </b> - lists global informations about MySQL system variables. Included are e.g. readings related to InnoDB-Home, datafile path,
  5718. memory- or cache-parameter and so on. The Output reports initially all available informations. Using the
  5719. <a href="#DbRepattr">attribute</a> "showVariables" the quantity of results can be limited to show only the desired values.
  5720. Further detailed informations of items meaning are explained
  5721. <a href=http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html>there</a>. <br>
  5722. <br><ul>
  5723. <b>Example</b> <br>
  5724. get &lt;name&gt; dbvars <br>
  5725. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  5726. # Only readings containing "version" and "query_cache" in name will be created
  5727. </li>
  5728. <br><br>
  5729. </ul>
  5730. <li><b> procinfo </b> - reports the existing database processes in a summary table (only MySQL). <br>
  5731. Typically only the own processes of the connection user (set in DbLog configuration file) will be
  5732. reported. If all precesses have to be reported, the global "PROCESS" right has to be granted to the
  5733. user. <br>
  5734. As of MariaDB 5.3 for particular SQL-Statements a progress reporting will be provided
  5735. (table row "PROGRESS"). So you can track, for instance, the degree of processing during an index
  5736. creation. <br>
  5737. Further informations can be found
  5738. <a href=https://mariadb.com/kb/en/mariadb/show-processlist/>there</a>. <br>
  5739. </li>
  5740. <br><br>
  5741. <li><b> svrinfo </b> - common database server informations, e.g. DBMS-version, server address and port and so on. The quantity of elements to get depends
  5742. on the database type. Using the <a href="#DbRepattr">attribute</a> "showSvrInfo" the quantity of results can be limited to show only
  5743. the desired values. Further detailed informations of items meaning are explained
  5744. <a href=https://msdn.microsoft.com/en-us/library/ms711681(v=vs.85).aspx>there</a>. <br>
  5745. <br><ul>
  5746. <b>Example</b> <br>
  5747. get &lt;name&gt; svrinfo <br>
  5748. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  5749. # Only readings containing "SQL_CATALOG_TERM" and "NAME" in name will be created
  5750. </li>
  5751. <br><br>
  5752. </ul>
  5753. <li><b> tableinfo </b> - access detailed informations about tables in MySQL database which is connected by the DbRep-device.
  5754. All available tables in the connected database will be selected by default.
  5755. Using the<a href="#DbRepattr">attribute</a> "showTableInfo" the results can be limited to tables you want to show.
  5756. Further detailed informations of items meaning are explained <a href=http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html>there</a>. <br>
  5757. <br><ul>
  5758. <b>Example</b> <br>
  5759. get &lt;name&gt; tableinfo <br>
  5760. attr &lt;name&gt; showTableInfo current,history <br>
  5761. # Only informations related to tables "current" and "history" are going to be created
  5762. </li>
  5763. <br><br>
  5764. </ul>
  5765. <br>
  5766. </ul></ul>
  5767. </ul>
  5768. <a name="DbRepattr"></a>
  5769. <b>Attributes</b>
  5770. <br>
  5771. <ul>
  5772. Using the module specific attributes you are able to define the scope of evaluation and the aggregation. <br><br>
  5773. <b>Note for SQL-Wildcard Usage:</b> <br>
  5774. Within the attribute values of "device" and "reading" you may use SQL-Wildcard "%", Character "_" is not supported as a wildcard.
  5775. The character "%" stands for any characters. <br>
  5776. This rule is valid to all functions <b>except</b> "insert", "importFromFile" and "deviceRename". <br>
  5777. The function "insert" doesn't allow setting the mentioned attributes containing the wildcard "%". <br>
  5778. In readings the wildcard character "%" will be replaced by "/" to meet the rules of allowed characters in readings.
  5779. <br><br>
  5780. <ul><ul>
  5781. <li><b>aggregation </b> - Aggregation of Device/Reading-selections. Possible is hour, day, week, month or "no".
  5782. Delivers e.g. the count of database entries for a day (countEntries), Summation of
  5783. difference values of a reading (sumValue) and so on. Using aggregation "no" (default) an
  5784. aggregation don't happens but the output contaims all values of Device/Reading in the defined time period. </li> <br>
  5785. <li><b>allowDeletion </b> - unlocks the delete-function </li> <br>
  5786. <li><b>device </b> - Selection of a particular device. <br>
  5787. You can specify <a href="https://fhem.de/commandref.html#devspec">device specifications (devspec)</a>. <br>
  5788. Inside of device specifications a SQL wildcard (%) will be evaluated as a normal ASCII-character.
  5789. The device names are derived from device specification and the active devices in FHEM before
  5790. SQL selection will be carried out. </li> <br>
  5791. <ul>
  5792. <b>Examples:</b> <br>
  5793. <code>attr &lt;Name&gt; device TYPE=DbRep</code> <br>
  5794. # select datasets of active present devices with Type "DbRep" <br>
  5795. <code>attr &lt;Name&gt; device MySTP_5000</code> <br>
  5796. # select datasets of device "MySTP_5000" <br>
  5797. <code>attr &lt;Name&gt; device SMA.*</code> <br>
  5798. # select datasets of devices starting with "SMA" <br>
  5799. <code>attr &lt;Name&gt; device SMA_Energymeter,MySTP_5000</code> <br>
  5800. # select datasets of devices "SMA_Energymeter" and "MySTP_5000" <br>
  5801. <code>attr &lt;Name&gt; device %5000</code> <br>
  5802. # select datasets of devices ending with "5000" <br>
  5803. </ul>
  5804. <br><br>
  5805. <li><b>diffAccept </b> - valid for function diffValue. diffAccept determines the threshold, up to that a calaculated difference between two
  5806. straight sequently datasets should be commenly accepted (default = 20). <br>
  5807. Hence faulty DB entries with a disproportional high difference value will be eliminated and don't tamper the result.
  5808. If a threshold overrun happens, the reading "diff_overrun_limit_&lt;diffLimit&gt;" will be generated
  5809. (&lt;diffLimit&gt; will be substituted with the present prest attribute value). <br>
  5810. The reading contains a list of relevant pair of values. Using verbose=3 this list will also be reported in the FHEM
  5811. logfile.
  5812. </li><br>
  5813. <ul>
  5814. Example report in logfile if threshold of diffAccept=10 overruns: <br><br>
  5815. DbRep Rep.STP5000.etotal -> data ignored while calc diffValue due to threshold overrun (diffAccept = 10): <br>
  5816. 2016-04-09 08:50:50 0.0340 -> 2016-04-09 12:42:01 13.3440 <br><br>
  5817. # The first dataset with a value of 0.0340 is untypical low compared to the next value of 13.3440 and results a untypical
  5818. high difference value. <br>
  5819. # Now you have to decide if the (second) dataset should be deleted, ignored of the attribute diffAccept should be adjusted.
  5820. </ul><br>
  5821. <li><b>disable </b> - deactivates the module </li> <br>
  5822. <li><b>dumpComment </b> - User-comment. It will be included in the header of the created dumpfile by
  5823. command "dumpMySQL clientSide". </li> <br>
  5824. <li><b>dumpDirLocal </b> - Target directory of database dumps by command "dumpMySQL clientSide"
  5825. (default: "{global}{modpath}/log/" on the FHEM-Server). <br>
  5826. In this directory also the internal version administration searches for old backup-files
  5827. and deletes them if the number exceeds attribute "dumpFilesKeep".
  5828. The attribute is also relevant to publish a local mounted directory "dumpDirRemote" to
  5829. DbRep. </li> <br>
  5830. <li><b>dumpDirRemote </b> - Target directory of database dumps by command "dumpMySQL serverSide"
  5831. (default: the Home-directory of MySQL-Server on the MySQL-Host). </li> <br>
  5832. <li><b>dumpMemlimit </b> - tolerable memory consumption for the SQL-script during generation period (default: 100000 characters).
  5833. Please adjust this parameter if you may notice memory bottlenecks and performance problems based
  5834. on it on your specific hardware. </li> <br>
  5835. <li><b>dumpSpeed </b> - Number of Lines which will be selected in source database with one select by dump-command
  5836. "dumpMySQL ClientSide" (default: 10000).
  5837. This parameter impacts the run-time and consumption of resources directly. </li> <br>
  5838. <li><b>dumpFilesKeep </b> - The specified number of dumpfiles remain in the dump directory (default: 3).
  5839. If there more (older) files has been found, these files will be deleted after a new database dump
  5840. was created successfully.
  5841. The global attrubute "archivesort" will be considered. </li> <br>
  5842. <li><b>executeAfterDump </b> - you can specify a FHEM-command which should be executed <b>after dump</b>. <br>
  5843. Funktions have to be enclosed in {} .<br><br>
  5844. <ul>
  5845. <b>Example:</b> <br><br>
  5846. attr &lt;DbRep-device&gt; executeAfterDump set og_gz_westfenster off; <br>
  5847. attr &lt;DbRep-device&gt; executeAfterDump {adump ("&lt;DbRep-device&gt;")} <br><br>
  5848. # "adump" is a function defined in 99_myUtils.pm e.g.: <br>
  5849. <pre>
  5850. sub adump {
  5851. my ($name) = @_;
  5852. my $hash = $defs{$name};
  5853. # own function, e.g.
  5854. Log3($name, 3, "DbRep $name -> Dump finished");
  5855. return;
  5856. }
  5857. </pre>
  5858. </ul>
  5859. </li>
  5860. <li><b>executeBeforeDump </b> - you can specify a FHEM-command which should be executed <b>before dump</b>. <br>
  5861. Funktions have to be enclosed in {} .<br><br>
  5862. <ul>
  5863. <b>Example:</b> <br><br>
  5864. attr &lt;DbRep-device&gt; executeBeforeDump set og_gz_westfenster on; <br>
  5865. attr &lt;DbRep-device&gt; executeBeforeDump {bdump ("&lt;DbRep-device&gt;")} <br><br>
  5866. # "bdump" is a function defined in 99_myUtils.pm e.g.: <br>
  5867. <pre>
  5868. sub bdump {
  5869. my ($name) = @_;
  5870. my $hash = $defs{$name};
  5871. # own function, e.g.
  5872. Log3($name, 3, "DbRep $name -> Dump starts now");
  5873. return;
  5874. }
  5875. </pre>
  5876. </ul>
  5877. </li>
  5878. <li><b>expimpfile </b> - Path/filename for data export/import </li> <br>
  5879. <li><b>ftpUse </b> - FTP Transfer after dump will be switched on (without SSL encoding). The created
  5880. database backup file will be transfered non-blocking to the FTP-Server (Attribut "ftpServer").
  5881. </li> <br>
  5882. <li><b>ftpUseSSL </b> - FTP Transfer with SSL encoding after dump. The created database backup file will be transfered
  5883. non-blocking to the FTP-Server (Attribut "ftpServer"). </li> <br>
  5884. <li><b>ftpUser </b> - User for FTP-server login, default: "anonymous". </li> <br>
  5885. <li><b>ftpDebug </b> - debugging of FTP communication for diagnostics. </li> <br>
  5886. <li><b>ftpDir </b> - directory on FTP-server in which the file will be send into (default: "/"). </li> <br>
  5887. <li><b>ftpPassive </b> - set if passive FTP is to be used </li> <br>
  5888. <li><b>ftpPort </b> - FTP-Port, default: 21 </li> <br>
  5889. <li><b>ftpPwd </b> - password of FTP-User, is not set by default </li> <br>
  5890. <li><b>ftpServer </b> - name or IP-address of FTP-server. <b>absolutely essential !</b> </li> <br>
  5891. <li><b>ftpTimeout </b> - timeout of FTP-connection in seconds (default: 30). </li> <br>
  5892. <a name="DbRepattrlimit"></a>
  5893. <li><b>limit </b> - limits the number of selected datasets by the "fetchrows" command (default 1000).
  5894. This limitation should prevent the browser session from overload and
  5895. avoids FHEMWEB from blocking. Please change the attribut according your requirements or change the
  5896. selection criteria (decrease evaluation period). </li> <br>
  5897. <li><b>optimizeTablesBeforeDump </b> - if set to "1", the database tables will be optimized before executing the dump
  5898. (default: 0).
  5899. Thereby the backup run-time time will be extended. <br><br>
  5900. <ul>
  5901. <b>Note</b> <br>
  5902. The table optimizing cause locking the tables and therefore to blocking of
  5903. FHEM if DbLog isn't working in asynchronous mode (DbLog-attribute "asyncMode") !
  5904. <br>
  5905. </ul>
  5906. </li> <br>
  5907. <li><b>reading </b> - Selection of a particular reading.
  5908. More than one reading are specified as a comma separated list. <br>
  5909. If SQL wildcard (%) is set in a list, it will be evaluated as a normal ASCII-character. <br>
  5910. </li> <br>
  5911. <ul>
  5912. <b>Examples:</b> <br>
  5913. <code>attr &lt;Name&gt; reading etotal</code> <br>
  5914. <code>attr &lt;Name&gt; reading et%</code> <br>
  5915. <code>attr &lt;Name&gt; reading etotal,etoday</code> <br>
  5916. </ul>
  5917. <br><br>
  5918. <li><b>readingNameMap </b> - the name of the analyzed reading can be overwritten for output </li> <br>
  5919. <li><b>role </b> - the role of the DbRep-device. Standard role is "Client". The role "Agent" is described
  5920. in section <a href="#DbRepAutoRename">DbRep-Agent</a>. </li> <br>
  5921. <li><b>readingPreventFromDel </b> - comma separated list of readings which are should prevent from deletion when a
  5922. new operation starts </li> <br>
  5923. <li><b>showproctime </b> - if set, the reading "sql_processing_time" shows the required execution time (in seconds)
  5924. for the sql-requests. This is not calculated for a single sql-statement, but the summary
  5925. of all sql-statements necessara for within an executed DbRep-function in background. </li> <br>
  5926. <li><b>showStatus </b> - limits the sample space of command "get ... dbstatus". SQL-Wildcard (%) can be used. </li> <br>
  5927. <ul>
  5928. Example: attr ... showStatus %uptime%,%qcache% <br>
  5929. # Only readings with containing "uptime" and "qcache" in name will be shown <br>
  5930. </ul><br>
  5931. <li><b>showVariables </b> - limits the sample space of command "get ... dbvars". SQL-Wildcard (%) can be used. </li> <br>
  5932. <ul>
  5933. Example: attr ... showVariables %version%,%query_cache% <br>
  5934. # Only readings with containing "version" and "query_cache" in name will be shown <br>
  5935. </ul><br>
  5936. <li><b>showSvrInfo </b> - limits the sample space of command "get ... svrinfo". SQL-Wildcard (%) can be used. </li> <br>
  5937. <ul>
  5938. Example: attr ... showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  5939. # Only readings with containing "SQL_CATALOG_TERM" and "NAME" in name will be shown <br>
  5940. </ul><br>
  5941. <li><b>showTableInfo </b> - limits the tablename which is selected by command "get ... tableinfo". SQL-Wildcard
  5942. (%) can be used. </li> <br>
  5943. <ul>
  5944. Example: attr ... showTableInfo current,history <br>
  5945. # Only informations about tables "current" and "history" will be shown <br>
  5946. </ul><br>
  5947. <li><b>sqlResultFormat </b> - determines the formatting of the "set ... sqlCmd" command result. possible options are: <br><br>
  5948. <ul>
  5949. <b>separated </b> - every line of the result will be generated sequentially in a single
  5950. reading. (default) <br><br>
  5951. <b>mline </b> - the result will be generated as multiline in
  5952. <a href="#DbRepReadings">Reading</a> SqlResult.
  5953. Field separator is "|". <br><br>
  5954. <b>sline </b> - the result will be generated as singleline in
  5955. <a href="#DbRepReadings">Reading</a> SqlResult.
  5956. Field separator is "|" and the dataset is separated by "]|[". <br><br>
  5957. <b>table </b> - the result will be generated as an table in
  5958. <a href="#DbRepReadings">Reading</a> SqlResult. <br><br>
  5959. <b>json </b> - creates <a href="#DbRepReadings">Reading</a> SqlResult as a JSON
  5960. coded hash.
  5961. Every hash-element consists of the serial number of the dataset (key)
  5962. and its value. </li> <br><br>
  5963. To process the result, you may use a userExitFn in 99_myUtils for example: <br>
  5964. <pre>
  5965. sub resfromjson {
  5966. my ($name,$reading,$value) = @_;
  5967. my $hash = $defs{$name};
  5968. if ($reading eq "SqlResult") {
  5969. # only reading SqlResult contains JSON encoded data
  5970. my $data = decode_json($value);
  5971. foreach my $k (keys(%$data)) {
  5972. # use your own processing from here for every hash-element
  5973. # e.g. output of every element that contains "Cam"
  5974. my $ke = $data->{$k};
  5975. if($ke =~ m/Cam/i) {
  5976. my ($res1,$res2) = split("\\|", $ke);
  5977. Log3($name, 1, "$name - extract element $k by userExitFn: ".$res1." ".$res2);
  5978. }
  5979. }
  5980. }
  5981. return;
  5982. }
  5983. </pre>
  5984. </ul>
  5985. <br>
  5986. <li><b>timestamp_begin </b> - begin of data selection (*) </li> <br>
  5987. <li><b>timestamp_end </b> - end of data selection. If not set the current date/time combination will be used. (*) </li> <br>
  5988. (*) The format of timestamp is as used with DbLog "YYYY-MM-DD HH:MM:SS". For the attributes "timestamp_begin", "timestamp_end"
  5989. you can also use one of the following entries. The timestamp-attribute will be dynamically set to: <br><br>
  5990. <ul>
  5991. <b>current_year_begin</b> : matches "&lt;current year&gt;-01-01 00:00:00" <br>
  5992. <b>current_year_end</b> : matches "&lt;current year&gt;-12-31 23:59:59" <br>
  5993. <b>previous_year_begin</b> : matches "&lt;previous year&gt;-01-01 00:00:00" <br>
  5994. <b>previous_year_end</b> : matches "&lt;previous year&gt;-12-31 23:59:59" <br>
  5995. <b>current_month_begin</b> : matches "&lt;current month first day&gt; 00:00:00" <br>
  5996. <b>current_month_end</b> : matches "&lt;current month last day&gt; 23:59:59" <br>
  5997. <b>previous_month_begin</b> : matches "&lt;previous month first day&gt; 00:00:00" <br>
  5998. <b>previous_month_end</b> : matches "&lt;previous month last day&gt; 23:59:59" <br>
  5999. <b>current_week_begin</b> : matches "&lt;first day of current week&gt; 00:00:00" <br>
  6000. <b>current_week_end</b> : matches "&lt;last day of current week&gt; 23:59:59" <br>
  6001. <b>previous_week_begin</b> : matches "&lt;first day of previous week&gt; 00:00:00" <br>
  6002. <b>previous_week_end</b> : matches "&lt;last day of previous week&gt; 23:59:59" <br>
  6003. <b>current_day_begin</b> : matches "&lt;current day&gt; 00:00:00" <br>
  6004. <b>current_day_end</b> : matches "&lt;current day&gt; 23:59:59" <br>
  6005. <b>previous_day_begin</b> : matches "&lt;previous day&gt; 00:00:00" <br>
  6006. <b>previous_day_end</b> : matches "&lt;previous day&gt; 23:59:59" <br>
  6007. <b>current_hour_begin</b> : matches "&lt;current hour&gt;:00:00" <br>
  6008. <b>current_hour_end</b> : matches "&lt;current hour&gt;:59:59" <br>
  6009. <b>previous_hour_begin</b> : matches "&lt;previous hour&gt;:00:00" <br>
  6010. <b>previous_hour_end</b> : matches "&lt;previous hour&gt;:59:59" <br> </ul><br>
  6011. Make sure that "timestamp_begin" < "timestamp_end" is fulfilled. <br><br>
  6012. <ul>
  6013. <b>Example:</b> <br><br>
  6014. attr &lt;DbRep-device&gt; timestamp_begin current_year_begin <br>
  6015. attr &lt;DbRep-device&gt; timestamp_end current_year_end <br><br>
  6016. # Analyzes the database between the time limits of the current year. <br>
  6017. </ul>
  6018. <br><br>
  6019. <b>Note </b> <br>
  6020. If the attribute "timeDiffToNow" will be set, the attributes "timestamp_begin" respectively "timestamp_end" will be deleted if they were set before.
  6021. The setting of "timestamp_begin" respectively "timestamp_end" causes the deletion of attribute "timeDiffToNow" if it was set before as well.
  6022. <br><br>
  6023. <li><b>timeDiffToNow </b> - the begin of data selection will be set to the timestamp "&lt;current time&gt; -
  6024. &lt;timeDiffToNow&gt;" dynamically (in seconds). Thereby always the last
  6025. &lt;timeDiffToNow&gt;-seconds will be considered (e.g. if set to 86400, always the last
  6026. 24 hours should assumed). The Timestamp calculation will be done dynamically at execution
  6027. time. </li> <br>
  6028. <li><b>timeOlderThan </b> - the end of data selection will be set to the timestamp "&lt;aktuelle Zeit&gt; -
  6029. &lt;timeOlderThan&gt;" dynamically (in seconds). Always the datasets up to timestamp
  6030. "&lt;current time&gt; - &lt;timeOlderThan&gt;" will be considered (e.g. if set to
  6031. 86400, all datasets older than one day will be considered). The Timestamp calculation
  6032. will be done dynamically at execution time. </li> <br>
  6033. <li><b>timeout </b> - set the timeout-value for Blocking-Call Routines in background in seconds (default 86400) </li> <br>
  6034. <li><b>userExitFn </b> - provides an interface to execute user specific program code. <br>
  6035. To activate the interfaace at first you should implement the subroutine which will be
  6036. called by the interface in your 99_myUtls.pm as shown in by the example: <br>
  6037. <pre>
  6038. sub UserFunction {
  6039. my ($name,$reading,$value) = @_;
  6040. my $hash = $defs{$name};
  6041. ...
  6042. # e.g. output transfered data
  6043. Log3 $name, 1, "UserExitFn $name called - transfer parameter are Reading: $reading, Value: $value " ;
  6044. ...
  6045. return;
  6046. }
  6047. </pre>
  6048. The interface activation takes place by setting the subroutine name in the attribute.
  6049. Optional you may set a Reading:Value combination (Regex) as argument. If no Regex is
  6050. specified, all value combinations will be evaluated as "true" (related to .*:.*).
  6051. <br><br>
  6052. <ul>
  6053. <b>Example:</b> <br>
  6054. attr <device> userExitFn UserFunction .*:.* <br>
  6055. # "UserFunction" is the name of subroutine in 99_myUtils.pm.
  6056. </ul>
  6057. <br>
  6058. The interface works generally without and independent from Events.
  6059. If the attribute is set, after every reading generation the Regex will be evaluated.
  6060. If the evaluation was "true", set subroutine will be called.
  6061. For further processing following parameters will be forwarded to the function: <br><br>
  6062. <ul>
  6063. <li>$name - the name of the DbRep-Device </li>
  6064. <li>$reading - the name of the created reading </li>
  6065. <li>$value - the value of the reading </li>
  6066. </ul>
  6067. </li>
  6068. <br><br>
  6069. </ul></ul></ul>
  6070. <a name="DbRepReadings"></a>
  6071. <b>Readings</b>
  6072. <br>
  6073. <ul>
  6074. Regarding to the selected operation the reasults will be shown as readings. At the beginning of a new operation all old readings will be deleted to avoid
  6075. that unsuitable or invalid readings would remain.<br><br>
  6076. In addition the following readings will be created: <br><br>
  6077. <ul><ul>
  6078. <li><b>state </b> - contains the current state of evaluation. If warnings are occured (state = Warning) compare Readings
  6079. "diff_overrun_limit_&lt;diffLimit&gt;" and "less_data_in_period" </li> <br>
  6080. <li><b>errortext </b> - description about the reason of an error state </li> <br>
  6081. <li><b>background_processing_time </b> - the processing time spent for operations in background/forked operation </li> <br>
  6082. <li><b>sql_processing_time </b> - the processing time wasted for all sql-statements used for an operation </li> <br>
  6083. <li><b>diff_overrun_limit_&lt;diffLimit&gt;</b> - contains a list of pairs of datasets which have overrun the threshold (&lt;diffLimit&gt;)
  6084. of calculated difference each other determined by attribute "diffAccept" (default=20). </li> <br>
  6085. <li><b>less_data_in_period </b> - contains a list of time periods within only one dataset was found. The difference calculation considers
  6086. the last value of the aggregation period before the current one. Valid for function "diffValue". </li> <br>
  6087. <li><b>SqlResult </b> - result of the last executed sqlCmd-command. The formatting can be specified
  6088. by <a href="#DbRepattr">attribute</a> "sqlResultFormat" </li> <br>
  6089. <li><b>sqlCmd </b> - contains the last executed sqlCmd-command </li> <br>
  6090. </ul></ul>
  6091. <br><br>
  6092. </ul>
  6093. <a name="DbRepAutoRename"></a>
  6094. <b>DbRep Agent - automatic change of device names in databases and DbRep-definitions after FHEM "rename" command</b>
  6095. <br>
  6096. <ul>
  6097. By the attribute "role" the role of DbRep-device will be configured. The standard role is "Client". If the role has changed to "Agent", the DbRep device
  6098. react automatically on renaming devices in your FHEM installation. The DbRep device is now called DbRep-Agent. <br><br>
  6099. By the DbRep-Agent the following features are activated when a FHEM-device has being renamed: <br><br>
  6100. <ul><ul>
  6101. <li> in the database connected to the DbRep-Agent (Internal Database) dataset containing the old device name will be searched and renamed to the
  6102. to the new device name in <b>all</b> affected datasets. </li> <br>
  6103. <li> in the DbLog-Device assigned to the DbRep-Agent the definition will be changed to substitute the old device name by the new one. Thereby the logging of
  6104. the renamed device will be going on in the database. </li> <br>
  6105. <li> in other existing DbRep-definitions with Type "Client" a possibly set attribute "device = old device name" will be changed to "device = new device name".
  6106. Because of that, reporting definitions will be kept consistent automatically if devices are renamed in FHEM. </li> <br>
  6107. </ul></ul>
  6108. The following restrictions take place if a DbRep device was changed to an Agent by setting attribute "role" to "Agent". These conditions will be activated
  6109. and checked: <br><br>
  6110. <ul><ul>
  6111. <li> within a FHEM installation only one DbRep-Agent can be configured for every defined DbLog-database. That means, if more than one DbLog-database is present,
  6112. you could define same numbers of DbRep-Agents as well as DbLog-devices are defined. </li> <br>
  6113. <li> after changing to DbRep-Agent role only the set-command "renameDevice" will be available and as well as a reduced set of module specific attributes will be
  6114. permitted. If a DbRep-device of privious type "Client" has changed an Agent, furthermore not permitted attributes will be deleted if set. </li> <br>
  6115. </ul></ul>
  6116. All activities like database changes and changes of other DbRep-definitions will be logged in FHEM Logfile with verbose=3. In order that the renameDevice
  6117. function don't running into timeout set the timeout attribute to an appropriate value, especially if there are databases with huge datasets to evaluate.
  6118. As well as all the other database operations of this module, the autorename operation will be executed nonblocking. <br><br>
  6119. <ul>
  6120. <b>Example </b> of definition of a DbRep-device as an Agent: <br><br>
  6121. <code>
  6122. define Rep.Agent DbRep LogDB <br>
  6123. attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen <br>
  6124. attr Rep.Agent icon security <br>
  6125. attr Rep.Agent role Agent <br>
  6126. attr Rep.Agent room DbLog <br>
  6127. attr Rep.Agent showproctime 1 <br>
  6128. attr Rep.Agent stateFormat { ReadingsVal("$name","state", undef) eq "running" ? "renaming" : ReadingsVal("$name","state", undef). " &raquo;; ProcTime: ".ReadingsVal("$name","sql_processing_time", undef)." sec"} <br>
  6129. attr Rep.Agent timeout 86400 <br>
  6130. </code>
  6131. <br>
  6132. </ul>
  6133. </ul>
  6134. =end html
  6135. =begin html_DE
  6136. <a name="DbRep"></a>
  6137. <h3>DbRep</h3>
  6138. <ul>
  6139. <br>
  6140. Zweck des Moduls ist es, den Inhalt von DbLog-Datenbanken nach bestimmten Kriterien zu durchsuchen, zu managen, das Ergebnis hinsichtlich verschiedener
  6141. Aggregationen auszuwerten und als Readings darzustellen. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und
  6142. die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende. <br><br>
  6143. Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der (SQL)-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt
  6144. werden (siehe <a href="#DbRepattr">Attribute</a>). <br>
  6145. Alle vorhandenen Readings werden vor einer neuen Operation gelöscht. Durch das Attribut "readingPreventFromDel" kann eine Komma separierte Liste von Readings
  6146. angegeben werden die nicht gelöscht werden sollen. <br><br>
  6147. Aktuell werden folgende Operationen unterstützt: <br><br>
  6148. <ul><ul>
  6149. <li> Selektion aller Datensätze innerhalb einstellbarer Zeitgrenzen. </li>
  6150. <li> Darstellung der Datensätze einer Device/Reading-Kombination innerhalb einstellbarer Zeitgrenzen. </li>
  6151. <li> Selektion der Datensätze unter Verwendung von dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt. </li>
  6152. <li> Berechnung der Anzahl von Datensätzen einer Device/Reading-Kombination unter Berücksichtigung von Zeitgrenzen
  6153. und verschiedenen Aggregationen. </li>
  6154. <li> Die Berechnung von Summen- , Differenz- , Maximum- , Minimum- und Durchschnittswerten von numerischen Readings
  6155. in Zeitgrenzen und verschiedenen Aggregationen. </li>
  6156. <li> Löschung von Datensätzen. Die Eingrenzung der Löschung kann durch Device und/oder Reading sowie fixer oder
  6157. dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt erfolgen. </li>
  6158. <li> Export von Datensätzen in ein File im CSV-Format </li>
  6159. <li> Import von Datensätzen aus File im CSV-Format </li>
  6160. <li> Umbenennen von Device-Namen in Datenbanksätzen </li>
  6161. <li> automatisches Umbenennen von Device-Namen in Datenbanksätzen und DbRep-Definitionen nach FHEM "rename"
  6162. Befehl (siehe <a href="#DbRepAutoRename">DbRep-Agent</a>) </li>
  6163. <li> Ausführen von beliebigen Benutzer spezifischen SQL-Kommandos </li>
  6164. <li> Backups der FHEM-Datenbank erstellen (MySQL) </li>
  6165. <li> senden des Dumpfiles zu einem FTP-Server nach dem Backup </li>
  6166. <li> Restore von serverSide-Backups (MySQL) </li>
  6167. <li> Optimierung der angeschlossenen Datenbank (optimizeTables, vacuum) </li>
  6168. <li> Ausgabe der existierenden Datenbankprozesse (MySQL) </li>
  6169. <li> leeren der current-Tabelle </li>
  6170. <li> Auffüllen der current-Tabelle mit einem (einstellbaren) Extrakt der history-Tabelle</li>
  6171. </ul></ul>
  6172. <br>
  6173. Zur Aktivierung der Funktion "Autorename" wird dem definierten DbRep-Device mit dem Attribut "role" die Rolle "Agent" zugewiesen. Die Standardrolle nach Definition
  6174. ist "Client". Mehr ist dazu im Abschnitt <a href="#DbRepAutoRename">DbRep-Agent</a> beschrieben. <br><br>
  6175. DbRep stellt dem Nutzer einen UserExit zur Verfügung. Über diese Schnittstelle kann der Nutzer in Abhängigkeit von
  6176. frei definierbaren Reading/Value-Kombinationen (Regex) eigenen Code zur Ausführung bringen. Diese Schnittstelle arbeitet
  6177. unabhängig von einer Eventgenerierung. Weitere Informationen dazu ist unter <a href="#DbRepattr">Attribut</a>
  6178. "userExitFn" beschrieben. <br><br>
  6179. FHEM-Forum: <br>
  6180. <a href="https://forum.fhem.de/index.php/topic,53584.msg452567.html#msg452567">Modul 93_DbRep - Reporting und Management von Datenbankinhalten (DbLog)</a>.<br><br>
  6181. <b>Voraussetzungen </b> <br><br>
  6182. Das Modul setzt den Einsatz einer oder mehrerer DbLog-Instanzen voraus. Es werden die Zugangsdaten dieser
  6183. Datenbankdefinition genutzt. <br>
  6184. Es werden nur Inhalte der Tabelle "history" berücksichtigt wenn nichts anderes beschrieben ist. <br><br>
  6185. Überblick welche anderen Perl-Module DbRep verwendet: <br><br>
  6186. Net::FTP (nur wenn FTP-Transfer nach Datenbank-Dump genutzt wird) <br>
  6187. Net::FTPSSL (nur wenn FTP-Transfer mit Verschlüsselung nach Datenbank-Dump genutzt wird) <br>
  6188. POSIX <br>
  6189. Time::HiRes <br>
  6190. Time::Local <br>
  6191. Scalar::Util <br>
  6192. DBI <br>
  6193. Blocking (FHEM-Modul) <br><br>
  6194. Aus Performancegründen sollten zusätzlich folgender Index erstellt werden: <br>
  6195. <code>
  6196. CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;
  6197. </code>
  6198. </ul>
  6199. <br>
  6200. <a name="DbRepdefine"></a>
  6201. <b>Definition</b>
  6202. <br>
  6203. <ul>
  6204. <code>
  6205. define &lt;name&gt; DbRep &lt;Name der DbLog-Instanz&gt;
  6206. </code>
  6207. <br><br>
  6208. (&lt;Name der DbLog-Instanz&gt; - es wird der Name der auszuwertenden DBLog-Datenbankdefinition angegeben <b>nicht</b> der Datenbankname selbst)
  6209. </ul>
  6210. <br><br>
  6211. <a name="DbRepset"></a>
  6212. <b>Set </b>
  6213. <ul>
  6214. Zur Zeit gibt es folgende Set-Kommandos. Über sie werden die Auswertungen angestoßen und definieren selbst die Auswertungsvariante.
  6215. Nach welchen Kriterien die Datenbankinhalte durchsucht werden und die Aggregation erfolgt, wird durch <a href="#DbRepattr">Attribute</a> gesteuert.
  6216. <br><br>
  6217. <ul><ul>
  6218. <li><b> averageValue </b> - berechnet den Durchschnittswert der Readingwerte (DB-Spalte "VALUE") in den gegebenen
  6219. Zeitgrenzen ( siehe <a href="#DbRepattr">Attribute</a>).
  6220. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  6221. angegeben sein. </li> <br>
  6222. <li><b> cancelDump </b> - bricht einen laufenden Datenbankdump ab. </li> <br>
  6223. <li><b> countEntries [history|current] </b> - liefert die Anzahl der Tabelleneinträge (default: history) in den gegebenen
  6224. Zeitgrenzen (siehe <a href="#DbRepattr">Attribute</a>).
  6225. Sind die Timestamps nicht gesetzt werden alle Einträge gezählt.
  6226. Beschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw. Reading
  6227. gehen in die Selektion mit ein. </li> <br>
  6228. <li><b> delEntries </b> - löscht alle oder die durch die <a href="#DbRepattr">Attribute</a> device und/oder
  6229. reading definierten Datenbankeinträge. Die Eingrenzung über Timestamps erfolgt
  6230. folgendermaßen: <br><br>
  6231. <ul>
  6232. "timestamp_begin" gesetzt: gelöscht werden DB-Einträge <b>ab</b> diesem Zeitpunkt bis zum aktuellen Datum/Zeit <br>
  6233. "timestamp_end" gesetzt : gelöscht werden DB-Einträge <b>bis</b> bis zu diesem Zeitpunkt <br>
  6234. beide Timestamps gesetzt : gelöscht werden DB-Einträge <b>zwischen</b> diesen Zeitpunkten <br>
  6235. <br>
  6236. Aus Sicherheitsgründen muss das <a href="#DbRepattr">Attribut</a> "allowDeletion"
  6237. gesetzt sein um die Löschfunktion freizuschalten. <br>
  6238. </li>
  6239. <br>
  6240. </ul>
  6241. <li><b> deviceRename </b> - benennt den Namen eines Device innerhalb der angeschlossenen Datenbank (Internal
  6242. DATABASE) um.
  6243. Der Gerätename wird immer in der <b>gesamten</b> Datenbank umgesetzt. Eventuell gesetzte
  6244. Zeitgrenzen oder Beschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw.
  6245. Reading werden nicht berücksichtigt. <br><br>
  6246. <ul>
  6247. <b>Eingabeformat: </b> set &lt;name&gt; deviceRename &lt;alter Devicename&gt;,&lt;neuer Devicename&gt; <br>
  6248. # Die Anzahl der umbenannten Device-Datensätze wird im Reading "device_renamed" ausgegeben. <br>
  6249. # Wird der umzubenennende Gerätename in der Datenbank nicht gefunden, wird eine WARNUNG im Reading "device_not_renamed" ausgegeben. <br>
  6250. # Entsprechende Einträge erfolgen auch im Logfile mit verbose=3
  6251. <br><br>
  6252. </li> <br>
  6253. </ul>
  6254. <li><b> diffValue </b> - berechnet den Differenzwert eines Readingwertes (DB-Spalte "Value") in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw "timeDiffToNow / timeOlderThan".
  6255. Es muss das auszuwertende Reading im Attribut "reading" angegeben sein.
  6256. Diese Funktion ist z.B. zur Auswertung von Eventloggings sinnvoll, deren Werte sich fortlaufend erhöhen und keine Wertdifferenzen wegschreiben. <br>
  6257. Es wird immer die Differenz aus dem Value-Wert des ersten verfügbaren Datensatzes und dem Value-Wert des letzten verfügbaren Datensatzes innerhalb der angegebenen
  6258. Zeitgrenzen/Aggregation gebildet, wobei ein Übertragswert der Vorperiode (Aggregation) zur darauf folgenden Aggregationsperiode
  6259. berücksichtigt wird sofern diese einen Value-Wert enhtält. <br>
  6260. Dabei wird ein Zählerüberlauf (Neubeginn bei 0) mit berücksichtigt (vergleiche <a href="#DbRepattr">Attribut</a> "diffAccept"). <br>
  6261. Wird in einer auszuwertenden Zeit- bzw. Aggregationsperiode nur ein Datensatz gefunden, kann die Differenz in Verbindung mit dem
  6262. Differenzübertrag der Vorperiode berechnet werden. in diesem Fall kann es zu einer logischen Ungenauigkeit in der Zuordnung der Differenz
  6263. zu der Aggregationsperiode kommen. Deswegen wird eine Warnung im "state" und das
  6264. Reading "less_data_in_period" mit einer Liste der betroffenen Perioden wird erzeugt. <br><br>
  6265. <ul>
  6266. <b>Hinweis: </b><br>
  6267. Im Auswertungs- bzw. Aggregationszeitraum (Tag, Woche, Monat, etc.) sollten dem Modul pro Periode mindestens ein Datensatz
  6268. zu Beginn und ein Datensatz gegen Ende des Aggregationszeitraumes zur Verfügung stehen um eine möglichst genaue Auswertung
  6269. der Differenzwerte vornehmen zu können.
  6270. <br>
  6271. <br>
  6272. </li>
  6273. </ul>
  6274. <li><b> dumpMySQL [clientSide | serverSide]</b>
  6275. - erstellt einen Dump der angeschlossenen MySQL-Datenbank. <br>
  6276. Abhängig von der ausgewählten Option wird der Dump auf der Client- bzw. Serverseite erstellt. <br>
  6277. Die Varianten unterscheiden sich hinsichtlich des ausführenden Systems, des Erstellungsortes, der
  6278. Attributverwendung, des erzielten Ergebnisses und der benötigten Hardwareressourcen. <br>
  6279. Die Option "clientSide" benötigt z.B. eine leistungsfähigere Hardware des FHEM-Servers, sichert aber alle
  6280. Tabellen inklusive eventuell angelegter Views.
  6281. <br><br>
  6282. <ul>
  6283. <b><u>Option clientSide</u></b> <br>
  6284. Der Dump wird durch den Client (FHEM-Rechner) erstellt und per default im log-Verzeichnis des Clients
  6285. gespeichert.
  6286. Das Zielverzeichnis kann mit dem <a href="#DbRepattr">Attribut</a> "dumpDirLocal" verändert werden und muß auf
  6287. dem Client durch FHEM beschreibbar sein. <br>
  6288. Vor dem Dump kann eine Tabellenoptimierung ("optimizeTablesBeforeDump") oder ein FHEM-Kommando
  6289. ("executeBeforeDump") optional zugeschaltet werden . <br><br>
  6290. <b>Achtung ! <br>
  6291. Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die
  6292. Tabellenoptimierung verwendet wird ! </b> <br><br>
  6293. Nach dem Dump kann ebenfalls ein FHEM-Kommando (siehe "executeAfterDump") ausgeführt werden. <br>
  6294. Über weitere <a href="#DbRepattr">Attribute</a> kann das Laufzeitverhalten der Funktion beeinflusst
  6295. werden um eine Optimierung bezüglich Performance und Ressourcenbedarf zu erreichen. <br>
  6296. Die für "dumpMySQL clientSide" relevanten Attribute sind "dumpComment", "dumpDirLocal", "dumpMemlimit",
  6297. "dumpSpeed ", "dumpFilesKeep", "executeBeforeDump", "executeAfterDump" und "optimizeTablesBeforeDump". <br>
  6298. Nach einem erfolgreichen Dump werden alte Dumpfiles gelöscht und nur die Anzahl "dumpFilesKeep" (default: 3)
  6299. verbleibt im Zielverzeichnis "dumpDirLocal". <br><br>
  6300. Die <b>Namenskonvention der Dumpfiles</b> ist: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sql <br><br>
  6301. Das erzeugte Dumpfile kann z.B. mit: <br><br>
  6302. <ul>
  6303. mysql -u &lt;user&gt; -p &lt;dbname&gt; < &lt;filename&gt;.sql <br><br>
  6304. </ul>
  6305. auf dem MySQL-Server ausgeführt werden um die Datenbank aus dem Dump wiederherzustellen. <br><br>
  6306. <br>
  6307. <b><u>Option serverSide</u></b> <br>
  6308. Der Dump wird durch den MySQL-Server erstellt und per default im Home-Verzeichnis des MySQL-Servers
  6309. gespeichert. <br>
  6310. Es wird die gesamte history-Tabelle (nicht current-Tabelle) <b>im CSV-Format</b> ohne
  6311. Einschränkungen exportiert. <br>
  6312. Vor dem Dump kann eine Tabellenoptimierung ("Attribut optimizeTablesBeforeDump")
  6313. optional zugeschaltet werden . <br><br>
  6314. <b>Achtung ! <br>
  6315. Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die
  6316. Tabellenoptimierung verwendet wird ! </b> <br><br>
  6317. Vor und nach dem Dump kann ein FHEM-Kommando (siehe "executeBeforeDump", "executeAfterDump") ausgeführt
  6318. werden. <br>
  6319. Die für "dumpMySQL serverSide" relevanten Attribute sind "dumpDirRemote", "dumpDirLocal",
  6320. "dumpFilesKeep", "optimizeTablesBeforeDump", "executeBeforeDump" und "executeAfterDump". <br><br>
  6321. Das Zielverzeichnis kann mit dem <a href="#DbRepattr">Attribut</a> "dumpDirRemote" verändert werden.
  6322. Es muß sich auf dem MySQL-Host gefinden und durch den MySQL-Serverprozess beschreibbar sein. <br>
  6323. Der verwendete Datenbankuser benötigt das "FILE"-Privileg. <br><br>
  6324. <b>Hinweis:</b> <br>
  6325. Soll die interne Versionsverwaltung des Moduls genutzt und die Größe des erzeugten Dumpfiles
  6326. ausgegeben werden, ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten
  6327. und im <a href="#DbRepattr">Attribut</a> "dumpDirLocal" dem DbRep-Device bekannt zu machen. <br>
  6328. Gleiches gilt wenn der FTP-Transfer nach dem Dump genutzt werden soll (Attribut "ftpUse" bzw. "ftpUseSSL").
  6329. <br><br>
  6330. <ul>
  6331. <b>Beispiel: </b> <br>
  6332. attr &lt;DbRep-device&gt; dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM/ <br>
  6333. attr &lt;DbRep-device&gt; dumpDirLocal /sds1/backup/dumps_FHEM/ <br>
  6334. attr &lt;DbRep-device&gt; dumpFilesKeep 2 <br><br>
  6335. # Der Dump wird remote auf dem MySQL-Server im Verzeichnis '/volume1/ApplicationBackup/dumps_FHEM/'
  6336. erstellt. <br>
  6337. # Die interne Versionsverwaltung sucht im lokal gemounteten Verzeichnis '/sds1/backup/dumps_FHEM/'
  6338. vorhandene Dumpfiles und löscht diese bis auf die zwei letzten Versionen. <br>
  6339. <br>
  6340. </ul>
  6341. Wird die interne Versionsverwaltung genutzt, werden nach einem erfolgreichen Dump alte Dumpfiles gelöscht
  6342. und nur die Anzahl "dumpFilesKeep" (default: 3) verbleibt im Zielverzeichnis "dumpDirRemote".
  6343. FHEM benötigt in diesem Fall Schreibrechte auf dem Verzeichnis "dumpDirLocal". <br><br>
  6344. Die <b>Namenskonvention der Dumpfiles</b> ist: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.csv <br><br>
  6345. Ein Restore der Datenbank aus diesem Backup kann durch den Befehl: <br><br>
  6346. <ul>
  6347. set &lt;name&gt; &lt;restoreMySQL&gt; &lt;filename&gt;.csv <br><br>
  6348. </ul>
  6349. gestartet werden. <br><br>
  6350. <b><u>FTP Transfer nach Dump</u></b> <br>
  6351. Wenn diese Möglichkeit genutzt werden soll, ist das <a href="#DbRepattr">Attribut</a> "ftpUse" oder
  6352. "ftpUseSSL" zu setzen. Letzteres gilt wenn Verschlüsselung genutzt werden soll. <br>
  6353. Weitere <a href="#DbRepattr">Attribute</a> sind: <br><br>
  6354. <ul>
  6355. <table>
  6356. <colgroup> <col width=5%> <col width=95%> </colgroup>
  6357. <tr><td> ftpUse </td><td>: FTP Transfer nach dem Dump wird eingeschaltet (ohne SSL Verschlüsselung) </td></tr>
  6358. <tr><td> ftpUser </td><td>: User zur Anmeldung am FTP-Server, default: anonymous </td></tr>
  6359. <tr><td> ftpUseSSL </td><td>: FTP Transfer mit SSL Verschlüsselung nach dem Dump wird eingeschaltet </td></tr>
  6360. <tr><td> ftpDebug </td><td>: Debugging des FTP Verkehrs zur Fehlersuche </td></tr>
  6361. <tr><td> ftpDir </td><td>: Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: "/") </td></tr>
  6362. <tr><td> ftpPassive </td><td>: setzen wenn passives FTP verwendet werden soll </td></tr>
  6363. <tr><td> ftpPort </td><td>: FTP-Port, default: 21 </td></tr>
  6364. <tr><td> ftpPwd </td><td>: Passwort des FTP-Users, default nicht gesetzt </td></tr>
  6365. <tr><td> ftpServer </td><td>: Name oder IP-Adresse des FTP-Servers. <b>notwendig !</b> </td></tr>
  6366. <tr><td> ftpTimeout </td><td>: Timeout für die FTP-Verbindung in Sekunden (default: 30). </td></tr>
  6367. </table>
  6368. </ul>
  6369. <br>
  6370. <br>
  6371. </ul>
  6372. </li><br>
  6373. <li><b> exportToFile </b> - exportiert DB-Einträge im CSV-Format in den gegebenen Zeitgrenzen.
  6374. Einschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw. Reading gehen in die Selektion mit ein.
  6375. Der Filename wird durch das <a href="#DbRepattr">Attribut</a> "expimpfile" bestimmt. </li><br>
  6376. <li><b> fetchrows [history|current] </b> - liefert <b>alle</b> Tabelleneinträge (default: history)
  6377. in den gegebenen Zeitgrenzen (siehe <a href="#DbRepattr">Attribute</a>).
  6378. Eine evtl. gesetzte Aggregation wird <b>nicht</b> berücksichtigt. <br><br>
  6379. <b>Hinweis:</b> <br>
  6380. Auch wenn das Modul bezüglich der Datenbankabfrage nichtblockierend arbeitet, kann eine
  6381. zu große Ergebnismenge (Anzahl Zeilen bzw. Readings) die Browsersesssion bzw. FHEMWEB
  6382. blockieren. Aus diesem Grund wird die Ergebnismenge mit dem
  6383. <a href="#DbRepattrlimit">Attribut</a> "limit" begrenzt. Bei Bedarf kann dieses Attribut
  6384. geändert werden falls eine Anpassung der Selektionsbedingungen nicht möglich oder
  6385. gewünscht ist. <br><br>
  6386. </li> <br>
  6387. <li><b> insert </b> - Manuelles Einfügen eines Datensatzes in die Tabelle "history". Obligatorisch sind Eingabewerte für Datum, Zeit und Value.
  6388. Die Werte für die DB-Felder Type bzw. Event werden mit "manual" gefüllt, sowie die Werte für Device, Reading aus den gesetzten <a href="#DbRepattr">Attributen </a> genommen. <br><br>
  6389. <ul>
  6390. <b>Eingabeformat: </b> Datum,Zeit,Value,[Unit] <br>
  6391. # Unit ist optional, Attribute "reading" und "device" müssen gesetzt sein <br>
  6392. # Soll "Value=0" eingefügt werden, ist "Value = 0.0" zu verwenden. <br><br>
  6393. <b>Beispiel: </b> 2016-08-01,23:00:09,TestValue,TestUnit <br>
  6394. # Es sind KEINE Leerzeichen im Feldwert erlaubt !<br>
  6395. <br>
  6396. <b>Hinweis: </b><br>
  6397. Bei der Eingabe ist darauf zu achten dass im beabsichtigten Aggregationszeitraum (Tag, Woche, Monat, etc.) MINDESTENS zwei
  6398. Datensätze für die Funktion diffValue zur Verfügung stehen. Ansonsten kann keine Differenz berechnet werden und diffValue
  6399. gibt in diesem Fall "0" in der betroffenen Periode aus !
  6400. <br>
  6401. <br>
  6402. </li>
  6403. </ul>
  6404. <li><b> importFromFile </b> - importiert Datensätze im CSV-Format aus einem File in die Datenbank. Der Filename wird
  6405. durch das <a href="#DbRepattr">Attribut</a> "expimpfile" bestimmt. <br><br>
  6406. <ul>
  6407. <b>Datensatzformat: </b> "TIMESTAMP","DEVICE","TYPE","EVENT","READING","VALUE","UNIT" <br><br>
  6408. # Die Felder "TIMESTAMP","DEVICE","TYPE","EVENT","READING" und "VALUE" müssen gesetzt sein. Das Feld "UNIT" ist optional.
  6409. Der Fileinhalt wird als Transaktion importiert, d.h. es wird der Inhalt des gesamten Files oder, im Fehlerfall, kein Datensatz des Files importiert.
  6410. Wird eine umfangreiche Datei mit vielen Datensätzen importiert sollte KEIN verbose=5 gesetzt werden. Es würden in diesem Fall sehr viele Sätze in
  6411. das Logfile geschrieben werden was FHEM blockieren oder überlasten könnte. <br><br>
  6412. <b>Beispiel: </b> "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573","" <br>
  6413. <br>
  6414. </li> <br>
  6415. </ul>
  6416. <li><b> maxValue </b> - berechnet den Maximalwert eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen
  6417. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  6418. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  6419. angegeben sein.
  6420. Die Auswertung enthält den Zeitstempel des ermittelten Maximumwertes innerhalb der
  6421. Aggregation bzw. Zeitgrenzen.
  6422. Im Reading wird der Zeitstempel des <b>letzten</b> Auftretens vom Maximalwert ausgegeben
  6423. falls dieser Wert im Intervall mehrfach erreicht wird. </li> <br>
  6424. <li><b> minValue </b> - berechnet den Minimalwert eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen
  6425. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  6426. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  6427. angegeben sein.
  6428. Die Auswertung enthält den Zeitstempel des ermittelten Minimumwertes innerhalb der
  6429. Aggregation bzw. Zeitgrenzen.
  6430. Im Reading wird der Zeitstempel des <b>ersten</b> Auftretens vom Minimalwert ausgegeben
  6431. falls dieser Wert im Intervall mehrfach erreicht wird. </li> <br>
  6432. <li><b> optimizeTables </b> - optimiert die Tabellen in der angeschlossenen Datenbank (MySQL). <br><br>
  6433. <ul>
  6434. <b>Hinweis:</b> <br>
  6435. Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device
  6436. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden. <br><br>
  6437. </li>
  6438. </ul><br>
  6439. <li><b> readingRename </b> - benennt den Namen eines Readings innerhalb der angeschlossenen Datenbank (siehe Internal DATABASE) um.
  6440. Der Readingname wird immer in der <b>gesamten</b> Datenbank umgesetzt. Eventuell
  6441. gesetzte Zeitgrenzen oder Beschränkungen durch die <a href="#DbRepattr">Attribute</a>
  6442. Device bzw. Reading werden nicht berücksichtigt. <br><br>
  6443. <ul>
  6444. <b>Eingabeformat: </b> set &lt;name&gt; readingRename &lt;alter Readingname&gt;,&lt;neuer Readingname&gt; <br>
  6445. # Die Anzahl der umbenannten Device-Datensätze wird im Reading "reading_renamed"
  6446. ausgegeben. <br>
  6447. # Wird der umzubenennende Readingname in der Datenbank nicht gefunden, wird eine
  6448. WARNUNG im Reading "reading_not_renamed" ausgegeben. <br>
  6449. # Entsprechende Einträge erfolgen auch im Logfile mit verbose=3.
  6450. <br><br>
  6451. </li> <br>
  6452. </ul>
  6453. <li><b> restoreMySQL &lt;File&gt;.csv </b> - importiert den Inhalt der history-Tabelle aus einem serverSide-Backup. <br>
  6454. Die Funktion stellt über eine Drop-Down Liste eine Dateiauswahl für den Restore zur Verfügung.
  6455. Dazu ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten
  6456. und im <a href="#DbRepattr">Attribut</a> "dumpDirLocal" dem DbRep-Device bekannt zu machen. <br>
  6457. Es werden alle Files mit der Endung "csv" und deren Name mit der
  6458. verbundenen Datenbank beginnt (siehe Internal DATABASE), aufgelistet . <br><br>
  6459. </li><br>
  6460. <li><b> sqlCmd </b> - führt ein beliebiges Benutzer spezifisches Kommando aus. <br>
  6461. Enthält dieses Kommando eine Delete-Operation, muss zur Sicherheit das
  6462. <a href="#DbRepattr">Attribut</a> "allowDeletion" gesetzt sein. <br>
  6463. Bei der Ausführung dieses Kommandos werden keine Einschränkungen durch gesetzte Attribute
  6464. device und/oder reading berücksichtigt. <br>
  6465. Sollen die im Modul gesetzten <a href="#DbRepattr">Attribute</a> "timestamp_begin" bzw.
  6466. "timestamp_end" im Statement berücksichtigt werden, können die Platzhalter
  6467. "<b>§timestamp_begin§</b>" bzw. "<b>§timestamp_end§</b>" dafür verwendet werden. <br><br>
  6468. <ul>
  6469. <b>Beispiele für Statements: </b> <br><br>
  6470. <ul>
  6471. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-01-06 00:00:00" group by DEVICE having count(*) > 800 </li>
  6472. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-05-06 00:00:00" group by DEVICE </li>
  6473. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= §timestamp_begin§ group by DEVICE </li>
  6474. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE like "Te%t" order by `TIMESTAMP` desc </li>
  6475. <li>set &lt;name&gt; sqlCmd select * from history where `TIMESTAMP` > "2017-05-09 18:03:00" order by `TIMESTAMP` desc </li>
  6476. <li>set &lt;name&gt; sqlCmd select * from current order by `TIMESTAMP` desc </li>
  6477. <li>set &lt;name&gt; sqlCmd select sum(VALUE) as 'Einspeisung am 04.05.2017', count(*) as 'Anzahl' FROM history where `READING` = "Einspeisung_WirkP_Zaehler_Diff" and TIMESTAMP between '2017-05-04' AND '2017-05-05' </li>
  6478. <li>set &lt;name&gt; sqlCmd delete from current </li>
  6479. <li>set &lt;name&gt; sqlCmd delete from history where TIMESTAMP < "2016-05-06 00:00:00" </li>
  6480. <li>set &lt;name&gt; sqlCmd update history set VALUE='TestVa$$ue$' WHERE VALUE='TestValue' </li>
  6481. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE = "Test" </li>
  6482. <li>set &lt;name&gt; sqlCmd insert into history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES ('2017-05-09 17:00:14','Test','manuell','manuell','Tes§e','TestValue','°C') </li>
  6483. </ul>
  6484. <br>
  6485. Das Ergebnis des Statements wird im <a href="#DbRepReadings">Reading</a> "SqlResult" dargestellt.
  6486. Die Formatierung kann durch das <a href="#DbRepattr">Attribut</a> "sqlResultFormat" ausgewählt werden. <br><bR>
  6487. <b>Hinweis:</b> <br>
  6488. Auch wenn das Modul bezüglich der Datenbankabfrage nichtblockierend arbeitet, kann eine
  6489. zu große Ergebnismenge (Anzahl Zeilen bzw. Readings) die Browsersesssion bzw. FHEMWEB
  6490. blockieren. Wenn man sich unsicher ist, sollte man vorsorglich dem Statement ein Limit
  6491. hinzufügen. <br><br>
  6492. </li> <br>
  6493. </ul>
  6494. <li><b> sumValue </b> - berechnet die Summenwerte eines Readingwertes (DB-Spalte "VALUE") in den Zeitgrenzen
  6495. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  6496. Es muss das auszuwertende Reading im <a href="#DbRepattr">Attribut</a> "reading"
  6497. angegeben sein. Diese Funktion ist sinnvoll wenn fortlaufend Wertedifferenzen eines
  6498. Readings in die Datenbank geschrieben werden. </li> <br>
  6499. <li><b> tableCurrentFillup </b> - Die current-Tabelle wird mit einem Extrakt der history-Tabelle aufgefüllt.
  6500. Die <a href="#DbRepattr">Attribute</a> zur Zeiteinschränkung bzw. device, reading werden ausgewertet.
  6501. Dadurch kann der Inhalt des Extrakts beeinflusst werden. Im zugehörigen DbLog-Device sollte sollte das Attribut
  6502. "DbLogType=SampleFill/History" gesetzt sein. </li> <br>
  6503. <li><b> tableCurrentPurge </b> - löscht den Inhalt der current-Tabelle. Es werden keine Limitierungen, z.B. durch die Attribute "timestamp_begin",
  6504. "timestamp_end", device, reading, usw. , ausgewertet. </li> <br>
  6505. <li><b> vacuum </b> - optimiert die Tabellen in der angeschlossenen Datenbank (SQLite, PostgreSQL). <br><br>
  6506. <ul>
  6507. <b>Hinweis:</b> <br>
  6508. Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device
  6509. im asynchronen Modus betrieben werden um ein Blockieren von FHEM zu vermeiden. <br><br>
  6510. </li>
  6511. </ul><br>
  6512. <br>
  6513. </ul></ul>
  6514. <b>Für alle Auswertungsvarianten (Ausnahme sqlCmd) gilt: </b> <br>
  6515. Zusätzlich zu dem auszuwertenden Reading kann das Device mit angegeben werden um das Reporting nach diesen Kriterien einzuschränken.
  6516. Sind keine Zeitgrenzen-Attribute angegeben, wird '1970-01-01 01:00:00' und das aktuelle Datum/Zeit als Zeitgrenze genutzt.
  6517. <br><br>
  6518. <b>Hinweis: </b> <br>
  6519. In der Detailansicht kann ein Browserrefresh nötig sein um die Operationsergebnisse zu sehen sobald im DeviceOverview "state = done" angezeigt wird.
  6520. <br><br>
  6521. </ul>
  6522. <a name="DbRepget"></a>
  6523. <b>Get </b>
  6524. <ul>
  6525. Die Get-Kommandos von DbRep dienen dazu eine Reihe von Metadaten der verwendeten Datenbankinstanz abzufragen.
  6526. Dies sind zum Beispiel eingestellte Serverparameter, Servervariablen, Datenbankstatus- und Tabelleninformationen. Die verfügbaren get-Funktionen
  6527. sind von dem verwendeten Datenbanktyp abhängig. So ist für SQLite z.Zt. nur "svrinfo" verfügbar. Die Funktionen liefern nativ sehr viele Ausgabewerte,
  6528. die über über funktionsspezifische <a href="#DbRepattr">Attribute</a> abgrenzbar sind. Der Filter ist als kommaseparierte Liste anzuwenden.
  6529. Dabei kann SQL-Wildcard (%) verwendet werden.
  6530. <br><br>
  6531. <b>Hinweis: </b> <br>
  6532. Nach der Ausführung einer get-Funktion in der Detailsicht einen Browserrefresh durchführen um die Ergebnisse zu sehen !
  6533. <br><br>
  6534. <ul><ul>
  6535. <li><b> dbstatus </b> - listet globale Informationen zum MySQL Serverstatus (z.B. Informationen zum Cache, Threads, Bufferpools, etc. ).
  6536. Es werden zunächst alle verfügbaren Informationen berichtet. Mit dem <a href="#DbRepattr">Attribut</a> "showStatus" kann die
  6537. Ergebnismenge eingeschränkt werden, um nur gewünschte Ergebnisse abzurufen. Detailinformationen zur Bedeutung der einzelnen Readings
  6538. sind <a href=http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html>hier</a> verfügbar. <br>
  6539. <br><ul>
  6540. <b>Bespiel</b> <br>
  6541. get &lt;name&gt; dbstatus <br>
  6542. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  6543. # Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten
  6544. </li>
  6545. <br><br>
  6546. </ul>
  6547. <li><b> dbvars </b> - zeigt die globalen Werte der MySQL Systemvariablen. Enthalten sind zum Beispiel Angaben zum InnoDB-Home, dem Datafile-Pfad,
  6548. Memory- und Cache-Parameter, usw. Die Ausgabe listet zunächst alle verfügbaren Informationen auf. Mit dem
  6549. <a href="#DbRepattr">Attribut</a> "showVariables" kann die Ergebnismenge eingeschränkt werden um nur gewünschte Ergebnisse
  6550. abzurufen. Weitere Informationen zur Bedeutung der ausgegebenen Variablen sind
  6551. <a href=http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html>hier</a> verfügbar. <br>
  6552. <br><ul>
  6553. <b>Bespiel</b> <br>
  6554. get &lt;name&gt; dbvars <br>
  6555. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  6556. # Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten
  6557. </li>
  6558. <br><br>
  6559. </ul>
  6560. <li><b> procinfo </b> - listet die existierenden Datenbank-Prozesse in einer Tabelle auf (nur MySQL). <br>
  6561. Typischerweise werden nur die Prozesse des Verbindungsusers (angegeben in DbLog-Konfiguration)
  6562. ausgegeben. Sollen alle Prozesse angezeigt werden, ist dem User das globale Recht "PROCESS"
  6563. einzuräumen. <br>
  6564. Für bestimmte SQL-Statements wird seit MariaDB 5.3 ein Fortschrittsreporting (Spalte "PROGRESS")
  6565. ausgegeben. Zum Beispiel kann der Abarbeitungsgrad bei der Indexerstellung verfolgt werden. <br>
  6566. Weitere Informationen sind
  6567. <a href=https://mariadb.com/kb/en/mariadb/show-processlist/>hier</a> verfügbar. <br>
  6568. </li>
  6569. <br><br>
  6570. <li><b> svrinfo </b> - allgemeine Datenbankserver-Informationen wie z.B. die DBMS-Version, Serveradresse und Port usw. Die Menge der Listenelemente
  6571. ist vom Datenbanktyp abhängig. Mit dem <a href="#DbRepattr">Attribut</a> "showSvrInfo" kann die Ergebnismenge eingeschränkt werden.
  6572. Weitere Erläuterungen zu den gelieferten Informationen sind
  6573. <a href=https://msdn.microsoft.com/en-us/library/ms711681(v=vs.85).aspx>hier</a> zu finden. <br>
  6574. <br><ul>
  6575. <b>Bespiel</b> <br>
  6576. get &lt;name&gt; svrinfo <br>
  6577. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  6578. # Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten
  6579. </li>
  6580. <br><br>
  6581. </ul>
  6582. <li><b> tableinfo </b> - ruft Tabelleninformationen aus der mit dem DbRep-Device verbundenen Datenbank ab (MySQL).
  6583. Es werden per default alle in der verbundenen Datenbank angelegten Tabellen ausgewertet.
  6584. Mit dem <a href="#DbRepattr">Attribut</a> "showTableInfo" können die Ergebnisse eingeschränkt werden. Erläuterungen zu den erzeugten
  6585. Readings sind <a href=http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html>hier</a> zu finden. <br>
  6586. <br><ul>
  6587. <b>Bespiel</b> <br>
  6588. get &lt;name&gt; tableinfo <br>
  6589. attr &lt;name&gt; showTableInfo current,history <br>
  6590. # Es werden nur Information der Tabellen "current" und "history" angezeigt
  6591. </li>
  6592. <br><br>
  6593. </ul>
  6594. <br>
  6595. </ul></ul>
  6596. </ul>
  6597. <a name="DbRepattr"></a>
  6598. <b>Attribute</b>
  6599. <br>
  6600. <ul>
  6601. Über die modulspezifischen Attribute wird die Abgrenzung der Auswertung und die Aggregation der Werte gesteuert. <br><br>
  6602. <b>Hinweis zur SQL-Wildcard Verwendung:</b> <br>
  6603. Innerhalb der Attribut-Werte für "device" und "reading" kann SQL-Wildcards "%" angegeben werden.
  6604. Dabei wird "%" als Platzhalter für beliebig viele Zeichen verwendet.
  6605. Das Zeichen "_" wird nicht als SQL-Wildcard supported. <br>
  6606. Dies gilt für alle Funktionen <b>ausser</b> "insert", "importFromFile" und "deviceRename". <br>
  6607. Die Funktion "insert" erlaubt nicht, dass die genannten Attribute das Wildcard "%" enthalten. Character "_" wird als normales Zeichen gewertet.<br>
  6608. In Ergebnis-Readings wird das Wildcardzeichen "%" durch "/" ersetzt um die Regeln für erlaubte Zeichen in Readings einzuhalten.
  6609. <br><br>
  6610. <ul><ul>
  6611. <li><b>aggregation </b> - Zusammenfassung der Device/Reading-Selektionen in Stunden,Tages,Kalenderwochen,Kalendermonaten oder "no". Liefert z.B. die Anzahl der DB-Einträge am Tag (countEntries), Summation von Differenzwerten eines Readings (sumValue), usw. Mit Aggregation "no" (default) erfolgt keine Zusammenfassung in einem Zeitraum sondern die Ausgabe ergibt alle Werte eines Device/Readings zwischen den definierten Zeiträumen. </li> <br>
  6612. <li><b>allowDeletion </b> - schaltet die Löschfunktion des Moduls frei </li> <br>
  6613. <li><b>device </b> - Abgrenzung der DB-Selektionen auf ein bestimmtes Device. <br>
  6614. Es können <a href="https://fhem.de/commandref_DE.html#devspec">Geräte-Spezifikationen (devspec)</a>
  6615. angegeben werden. <br>
  6616. Innerhalb von Geräte-Spezifikationen wird SQL-Wildcard (%) als normales ASCII-Zeichen gewertet.
  6617. Die Devicenamen werden vor der Selektion aus der Geräte-Spezifikationen und den aktuell in FHEM
  6618. vorhandenen Devices abgeleitet. </li> <br>
  6619. <ul>
  6620. <b>Beispiele:</b> <br>
  6621. <code>attr &lt;Name&gt; device TYPE=DbRep</code> <br>
  6622. <code>attr &lt;Name&gt; device MySTP_5000</code> <br>
  6623. <code>attr &lt;Name&gt; device SMA.*,MySTP.*</code> <br>
  6624. <code>attr &lt;Name&gt; device SMA_Energymeter,MySTP_5000</code> <br>
  6625. <code>attr &lt;Name&gt; device %5000</code> <br>
  6626. </ul>
  6627. <br><br>
  6628. <li><b>diffAccept </b> - gilt für Funktion diffValue. diffAccept legt fest bis zu welchem Schwellenwert eine berechnete positive Werte-Differenz
  6629. zwischen zwei unmittelbar aufeinander folgenden Datensätzen akzeptiert werden soll (Standard ist 20). <br>
  6630. Damit werden fehlerhafte DB-Einträge mit einem unverhältnismäßig hohen Differenzwert von der Berechnung ausgeschlossen und
  6631. verfälschen nicht das Ergebnis. Sollten Schwellenwertüberschreitungen vorkommen, wird das Reading "diff_overrun_limit_&lt;diffLimit&gt;"
  6632. erstellt. (&lt;diffLimit&gt; wird dabei durch den aktuellen Attributwert ersetzt)
  6633. Es enthält eine Liste der relevanten Wertepaare. Mit verbose 3 werden diese Datensätze ebenfalls im Logfile protokolliert.
  6634. </li> <br>
  6635. <ul>
  6636. Beispiel Ausgabe im Logfile beim Überschreiten von diffAccept=10: <br><br>
  6637. DbRep Rep.STP5000.etotal -> data ignored while calc diffValue due to threshold overrun (diffAccept = 10): <br>
  6638. 2016-04-09 08:50:50 0.0340 -> 2016-04-09 12:42:01 13.3440 <br><br>
  6639. # Der erste Datensatz mit einem Wert von 0.0340 ist untypisch gering zum nächsten Wert 13.3440 und führt zu einem zu hohen
  6640. Differenzwert. <br>
  6641. # Es ist zu entscheiden ob der Datensatz gelöscht, ignoriert, oder das Attribut diffAccept angepasst werden sollte.
  6642. </ul><br>
  6643. <li><b>disable </b> - deaktiviert das Modul </li> <br>
  6644. <li><b>dumpComment </b> - User-Kommentar. Er wird im Kopf des durch den Befehl "dumpMyQL clientSide" erzeugten Dumpfiles
  6645. eingetragen. </li> <br>
  6646. <li><b>dumpDirLocal </b> - Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL clientSide".
  6647. default: "{global}{modpath}/log/" auf dem FHEM-Server. <br>
  6648. Ebenfalls werden in diesem Verzeichnis alte Backup-Files durch die interne Versionsverwaltung von
  6649. "dumpMySQL" gesucht und gelöscht wenn die gefundene Anzahl den Attributwert "dumpFilesKeep"
  6650. überschreitet. Das Attribut dient auch dazu ein lokal gemountetes Verzeichnis "dumpDirRemote"
  6651. DbRep bekannt zu machen. </li> <br>
  6652. <li><b>dumpDirRemote </b> - Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL serverSide".
  6653. default: das Home-Dir des MySQL-Servers auf dem MySQL-Host </li> <br>
  6654. <li><b>dumpMemlimit </b> - erlaubter Speicherverbrauch für SQL-Script zur Generierungszeit (default: 100000 Zeichen).
  6655. Bitte den Parameter anpassen, falls es zu Speicherengpässen und damit verbundenen Performanceproblemen
  6656. kommen sollte. </li> <br>
  6657. <li><b>dumpSpeed </b> - Anzahl der abgerufenen Zeilen aus der Quelldatenbank (default: 10000) pro Select durch "dumpMySQL ClientSide".
  6658. Dieser Parameter hat direkten Einfluß auf die Laufzeit und den Ressourcenverbrauch zur Laufzeit. </li> <br>
  6659. <li><b>dumpFilesKeep </b> - Es wird die angegeben Anzahl Dumpfiles im Dumpdir gelassen (default: 3). Sind mehr (ältere) Dumpfiles
  6660. vorhanden, werden diese gelöscht nachdem ein neuer Dump erfolgreich erstellt wurde. Das globale
  6661. Attribut "archivesort" wird berücksichtigt. </li> <br>
  6662. <li><b>executeAfterDump </b> - Es kann ein FHEM-Kommando angegeben werden welches <b>nach dem Dump</b> ausgeführt werden soll. <br>
  6663. Funktionen sind in {} einzuschließen.<br><br>
  6664. <ul>
  6665. <b>Beispiel:</b> <br><br>
  6666. attr &lt;DbRep-device&gt; executeAfterDump set og_gz_westfenster off; <br>
  6667. attr &lt;DbRep-device&gt; executeAfterDump {adump ("&lt;DbRep-device&gt;")} <br><br>
  6668. # "adump" ist eine in 99_myUtils definierte Funktion. <br>
  6669. <pre>
  6670. sub adump {
  6671. my ($name) = @_;
  6672. my $hash = $defs{$name};
  6673. # die eigene Funktion, z.B.
  6674. Log3($name, 3, "DbRep $name -> Dump ist beendet");
  6675. return;
  6676. }
  6677. </pre>
  6678. </ul>
  6679. </li>
  6680. <li><b>executeBeforeDump </b> - Es kann ein FHEM-Kommando angegeben werden welches <b>vor dem Dump</b> ausgeführt werden soll. <br>
  6681. Funktionen sind in {} einzuschließen.<br><br>
  6682. <ul>
  6683. <b>Beispiel:</b> <br><br>
  6684. attr &lt;DbRep-device&gt; executeBeforeDump set og_gz_westfenster on; <br>
  6685. attr &lt;DbRep-device&gt; executeBeforeDump {bdump ("&lt;DbRep-device&gt;")} <br><br>
  6686. # "bdump" ist eine in 99_myUtils definierte Funktion. <br>
  6687. <pre>
  6688. sub bdump {
  6689. my ($name) = @_;
  6690. my $hash = $defs{$name};
  6691. # die eigene Funktion, z.B.
  6692. Log3($name, 3, "DbRep $name -> Dump startet");
  6693. return;
  6694. }
  6695. </pre>
  6696. </ul>
  6697. </li>
  6698. <li><b>expimpfile </b> - Pfad/Dateiname für Export/Import in/aus einem File. </li> <br>
  6699. <li><b>ftpUse </b> - FTP Transfer nach dem Dump wird eingeschaltet (ohne SSL Verschlüsselung). Das erzeugte
  6700. Datenbank Backupfile wird non-blocking zum angegebenen FTP-Server (Attribut "ftpServer")
  6701. übertragen. </li> <br>
  6702. <li><b>ftpUseSSL </b> - FTP Transfer mit SSL Verschlüsselung nach dem Dump wird eingeschaltet. Das erzeugte
  6703. Datenbank Backupfile wird non-blocking zum angegebenen FTP-Server (Attribut "ftpServer")
  6704. übertragen. </li> <br>
  6705. <li><b>ftpUser </b> - User zur Anmeldung am FTP-Server, default: "anonymous". </li> <br>
  6706. <li><b>ftpDebug </b> - Debugging der FTP Kommunikation zur Fehlersuche. </li> <br>
  6707. <li><b>ftpDir </b> - Verzeichnis des FTP-Servers in welches das File übertragen werden soll (default: "/"). </li> <br>
  6708. <li><b>ftpPassive </b> - setzen wenn passives FTP verwendet werden soll </li> <br>
  6709. <li><b>ftpPort </b> - FTP-Port, default: 21 </li> <br>
  6710. <li><b>ftpPwd </b> - Passwort des FTP-Users, default nicht gesetzt </li> <br>
  6711. <li><b>ftpServer </b> - Name oder IP-Adresse des FTP-Servers. <b>notwendig !</b> </li> <br>
  6712. <li><b>ftpTimeout </b> - Timeout für die FTP-Verbindung in Sekunden (default: 30). </li> <br>
  6713. <a name="DbRepattrlimit"></a>
  6714. <li><b>limit </b> - begrenzt die Anzahl der resultierenden Datensätze im select-Statement von "fetchrows"
  6715. (default 1000). Diese Limitierung soll eine Überlastung der Browsersession und ein
  6716. blockieren von FHEMWEB verhindern. Bei Bedarf entsprechend ändern bzw. die
  6717. Selektionskriterien (Zeitraum der Auswertung) anpassen. </li> <br>
  6718. <li><b>optimizeTablesBeforeDump </b> - wenn "1", wird vor dem Datenbankdump eine Tabellenoptimierung ausgeführt (default: 0).
  6719. Dadurch verlängert sich die Laufzeit des Dump. <br><br>
  6720. <ul>
  6721. <b>Hinweis </b> <br>
  6722. Die Tabellenoptimierung führt zur Sperrung der Tabellen und damit zur Blockierung von
  6723. FHEM falls DbLog nicht im asynchronen Modus (DbLog-Attribut "asyncMode") betrieben wird !
  6724. <br>
  6725. </ul>
  6726. </li> <br>
  6727. <li><b>reading </b> - Abgrenzung der DB-Selektionen auf ein bestimmtes oder mehrere Readings.
  6728. Mehrere Readings werden als Komma separierte Liste angegeben. <br>
  6729. SQL Wildcard (%) wird in einer Liste als normales ASCII-Zeichen gewertet. <br>
  6730. </li> <br>
  6731. <ul>
  6732. <b>Beispiele:</b> <br>
  6733. <code>attr &lt;Name&gt; reading etotal</code> <br>
  6734. <code>attr &lt;Name&gt; reading et%</code> <br>
  6735. <code>attr &lt;Name&gt; reading etotal,etoday</code> <br>
  6736. </ul>
  6737. <br><br>
  6738. <li><b>readingNameMap </b> - der Name des ausgewerteten Readings wird mit diesem String für die Anzeige überschrieben </li> <br>
  6739. <li><b>readingPreventFromDel </b> - Komma separierte Liste von Readings die vor einer neuen Operation nicht gelöscht
  6740. werden sollen </li> <br>
  6741. <li><b>role </b> - die Rolle des DbRep-Device. Standard ist "Client". Die Rolle "Agent" ist im Abschnitt
  6742. <a href="#DbRepAutoRename">DbRep-Agent</a> beschrieben. </li> <br>
  6743. <li><b>showproctime </b> - wenn gesetzt, zeigt das Reading "sql_processing_time" die benötigte Abarbeitungszeit (in Sekunden)
  6744. für die SQL-Ausführung der durchgeführten Funktion. Dabei wird nicht ein einzelnes
  6745. SQl-Statement, sondern die Summe aller notwendigen SQL-Abfragen innerhalb der jeweiligen
  6746. Funktion betrachtet. </li> <br>
  6747. <li><b>showStatus </b> - grenzt die Ergebnismenge des Befehls "get ... dbstatus" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  6748. <ul>
  6749. Bespiel: attr ... showStatus %uptime%,%qcache% <br>
  6750. # Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten <br>
  6751. </ul><br>
  6752. <li><b>showVariables </b> - grenzt die Ergebnismenge des Befehls "get ... dbvars" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  6753. <ul>
  6754. Bespiel: attr ... showVariables %version%,%query_cache% <br>
  6755. # Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten <br>
  6756. </ul><br>
  6757. <li><b>showSvrInfo </b> - grenzt die Ergebnismenge des Befehls "get ... svrinfo" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  6758. <ul>
  6759. Bespiel: attr ... showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  6760. # Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten <br>
  6761. </ul><br>
  6762. <li><b>showTableInfo </b> - grenzt die Ergebnismenge des Befehls "get ... tableinfo" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  6763. <ul>
  6764. Bespiel: attr ... showTableInfo current,history <br>
  6765. # Es werden nur Information der Tabellen "current" und "history" angezeigt <br>
  6766. </ul><br>
  6767. <li><b>sqlResultFormat </b> - legt die Formatierung des Ergebnisses des Kommandos "set ... sqlCmd" fest.
  6768. Mögliche Optionen sind: <br><br>
  6769. <ul>
  6770. <b>separated </b> - die Ergebniszeilen werden als einzelne Readings fortlaufend
  6771. generiert. (default)<br><br>
  6772. <b>mline </b> - das Ergebnis wird als Mehrzeiler im <a href="#DbRepReadings">Reading</a>
  6773. SqlResult dargestellt. Feldtrenner ist "|". <br><br>
  6774. <b>sline </b> - das Ergebnis wird als Singleline im <a href="#DbRepReadings">Reading</a>
  6775. SqlResult dargestellt. Feldtrenner ist "|", Satztrenner ist"]|[". <br><br>
  6776. <b>table </b> - das Ergebnis wird als Tabelle im <a href="#DbRepReadings">Reading</a>
  6777. SqlResult dargestellt. <br><br>
  6778. <b>json </b> - erzeugt das <a href="#DbRepReadings">Reading</a> SqlResult als
  6779. JSON-kodierten Hash.
  6780. Jedes Hash-Element (Ergebnissatz) setzt sich aus der laufenden Nummer
  6781. des Datensatzes (Key) und dessen Wert zusammen. </li><br><br>
  6782. Die Weiterverarbeitung des Ergebnisses kann z.B. mit der folgenden userExitFn in 99_myUtils.pm erfolgen: <br>
  6783. <pre>
  6784. sub resfromjson {
  6785. my ($name,$reading,$value) = @_;
  6786. my $hash = $defs{$name};
  6787. if ($reading eq "SqlResult") {
  6788. # nur Reading SqlResult enthält JSON-kodierte Daten
  6789. my $data = decode_json($value);
  6790. foreach my $k (keys(%$data)) {
  6791. # ab hier eigene Verarbeitung für jedes Hash-Element
  6792. # z.B. Ausgabe jedes Element welches "Cam" enthält
  6793. my $ke = $data->{$k};
  6794. if($ke =~ m/Cam/i) {
  6795. my ($res1,$res2) = split("\\|", $ke);
  6796. Log3($name, 1, "$name - extract element $k by userExitFn: ".$res1." ".$res2);
  6797. }
  6798. }
  6799. }
  6800. return;
  6801. }
  6802. </pre>
  6803. </ul><br>
  6804. <li><b>timestamp_begin </b> - der zeitliche Beginn für die Datenselektion (*) </li> <br>
  6805. <li><b>timestamp_end </b> - das zeitliche Ende für die Datenselektion. Wenn nicht gesetzt wird immer die aktuelle
  6806. Datum/Zeit-Kombi für das Ende der Selektion eingesetzt. (*) </li> <br>
  6807. (*) Das Format von Timestamp ist wie in DbLog "YYYY-MM-DD HH:MM:SS". Für die Attribute "timestamp_begin", "timestamp_end"
  6808. kann ebenso eine der folgenden Eingaben verwendet werden. Dabei wird das timestamp-Attribut dynamisch belegt: <br><br>
  6809. <ul>
  6810. <b>current_year_begin</b> : entspricht "&lt;aktuelles Jahr&gt;-01-01 00:00:00" <br>
  6811. <b>current_year_end</b> : entspricht "&lt;aktuelles Jahr&gt;-12-31 23:59:59" <br>
  6812. <b>previous_year_begin</b> : entspricht "&lt;vorheriges Jahr&gt;-01-01 00:00:00" <br>
  6813. <b>previous_year_end</b> : entspricht "&lt;vorheriges Jahr&gt;-12-31 23:59:59" <br>
  6814. <b>current_month_begin</b> : entspricht "&lt;aktueller Monat erster Tag&gt; 00:00:00" <br>
  6815. <b>current_month_end</b> : entspricht "&lt;aktueller Monat letzter Tag&gt; 23:59:59" <br>
  6816. <b>previous_month_begin</b> : entspricht "&lt;Vormonat erster Tag&gt; 00:00:00" <br>
  6817. <b>previous_month_end</b> : entspricht "&lt;Vormonat letzter Tag&gt; 23:59:59" <br>
  6818. <b>current_week_begin</b> : entspricht "&lt;erster Tag der akt. Woche&gt; 00:00:00" <br>
  6819. <b>current_week_end</b> : entspricht "&lt;letzter Tag der akt. Woche&gt; 23:59:59" <br>
  6820. <b>previous_week_begin</b> : entspricht "&lt;erster Tag Vorwoche&gt; 00:00:00" <br>
  6821. <b>previous_week_end</b> : entspricht "&lt;letzter Tag Vorwoche&gt; 23:59:59" <br>
  6822. <b>current_day_begin</b> : entspricht "&lt;aktueller Tag&gt; 00:00:00" <br>
  6823. <b>current_day_end</b> : entspricht "&lt;aktueller Tag&gt; 23:59:59" <br>
  6824. <b>previous_day_begin</b> : entspricht "&lt;Vortag&gt; 00:00:00" <br>
  6825. <b>previous_day_end</b> : entspricht "&lt;Vortag&gt; 23:59:59" <br>
  6826. <b>current_hour_begin</b> : entspricht "&lt;aktuelle Stunde&gt;:00:00" <br>
  6827. <b>current_hour_end</b> : entspricht "&lt;aktuelle Stunde&gt;:59:59" <br>
  6828. <b>previous_hour_begin</b> : entspricht "&lt;vorherige Stunde&gt;:00:00" <br>
  6829. <b>previous_hour_end</b> : entspricht "&lt;vorherige Stunde&gt;:59:59" <br>
  6830. </ul><br>
  6831. Natürlich sollte man immer darauf achten dass "timestamp_begin" < "timestamp_end" ist. <br><br>
  6832. <ul>
  6833. <b>Beispiel:</b> <br><br>
  6834. attr &lt;DbRep-device&gt; timestamp_begin current_year_begin <br>
  6835. attr &lt;DbRep-device&gt; timestamp_end current_year_end <br><br>
  6836. # Wertet die Datenbank in den Zeitgrenzen des aktuellen Jahres aus. <br>
  6837. </ul>
  6838. <br><br>
  6839. <b>Hinweis </b> <br>
  6840. Wird das Attribut "timeDiffToNow" gesetzt, werden die evtentuell gesetzten Attribute "timestamp_begin" bzw. "timestamp_end" gelöscht.
  6841. Das Setzen von "timestamp_begin" bzw. "timestamp_end" bedingt die Löschung von Attribut "timeDiffToNow" wenn es vorher gesetzt war.
  6842. <br><br>
  6843. <li><b>timeDiffToNow </b> - der Selektionsbeginn wird auf den Zeitpunkt "&lt;aktuelle Zeit&gt; - &lt;timeDiffToNow&gt;"
  6844. gesetzt (in Sekunden). Es werden immer die letzten &lt;timeDiffToNow&gt;-Sekunden
  6845. berücksichtigt (z.b. 86400 wenn immer die letzten 24 Stunden in die Selektion eingehen
  6846. sollen). Die Timestampermittlung erfolgt dynamisch zum Ausführungszeitpunkt. </li> <br>
  6847. <li><b>timeOlderThan </b> - das Selektionsende wird auf den Zeitpunkt "&lt;aktuelle Zeit&gt; - &lt;timeOlderThan&gt;"
  6848. gesetzt (in Sekunden). Dadurch werden alle Datensätze bis zu dem Zeitpunkt "&lt;aktuelle
  6849. Zeit&gt; - &lt;timeOlderThan&gt;" berücksichtigt (z.b. wenn auf 86400 gesetzt werden alle
  6850. Datensätze die älter als ein Tag sind berücksichtigt). Die Timestampermittlung erfolgt
  6851. dynamisch zum Ausführungszeitpunkt. </li> <br>
  6852. <li><b>timeout </b> - das Attribut setzt den Timeout-Wert für die Blocking-Call Routinen in Sekunden
  6853. (Default: 86400) </li> <br>
  6854. <li><b>userExitFn </b> - stellt eine Schnittstelle zur Ausführung eigenen Usercodes zur Verfügung. <br>
  6855. Um die Schnittstelle zu aktivieren, wird zunächst die aufzurufende Subroutine in
  6856. 99_myUtls.pm nach folgendem Muster erstellt: <br>
  6857. <pre>
  6858. sub UserFunction {
  6859. my ($name,$reading,$value) = @_;
  6860. my $hash = $defs{$name};
  6861. ...
  6862. # z.B. übergebene Daten loggen
  6863. Log3 $name, 1, "UserExitFn $name called - transfer parameter are Reading: $reading, Value: $value " ;
  6864. ...
  6865. return;
  6866. }
  6867. </pre>
  6868. Die Aktivierung der Schnittstelle erfogt durch Setzen des Funktionsnames im Attribut.
  6869. Optional kann ein Reading:Value Regex als Argument angegeben werden. Wird kein Regex
  6870. angegeben, werden alle Wertekombinationen als "wahr" gewertet (entspricht .*:.*).
  6871. <br><br>
  6872. <ul>
  6873. <b>Beispiel:</b> <br>
  6874. attr <device> userExitFn UserFunction .*:.* <br>
  6875. # "UserFunction" ist die Subroutine in 99_myUtils.pm.
  6876. </ul>
  6877. <br>
  6878. Grundsätzlich arbeitet die Schnittstelle OHNE Eventgenerierung bzw. benötigt zur Funktion keinen
  6879. Event. Sofern das Attribut gesetzt ist, erfolgt Die Regexprüfung NACH der Erstellung eines
  6880. Readings. Ist die Prüfung WAHR, wird die angegebene Funktion aufgerufen.
  6881. Zur Weiterverarbeitung werden der aufgerufenenen Funktion folgende Variablen übergeben: <br><br>
  6882. <ul>
  6883. <li>$name - der Name des DbRep-Devices </li>
  6884. <li>$reading - der Namen des erstellen Readings </li>
  6885. <li>$value - der Wert des Readings </li>
  6886. </ul>
  6887. </li>
  6888. <br><br>
  6889. </ul></ul>
  6890. </ul>
  6891. <a name="DbRepReadings"></a>
  6892. <b>Readings</b>
  6893. <br>
  6894. <ul>
  6895. Abhängig von der ausgeführten DB-Operation werden die Ergebnisse in entsrechenden Readings dargestellt. Zu Beginn einer neuen Operation werden alle alten Readings
  6896. einer vorangegangenen Operation gelöscht um den Verbleib unpassender bzw. ungültiger Readings zu vermeiden. <br><br>
  6897. Zusätzlich werden folgende Readings erzeugt (Auswahl): <br><br>
  6898. <ul><ul>
  6899. <li><b>state </b> - enthält den aktuellen Status der Auswertung. Wenn Warnungen auftraten (state = Warning) vergleiche Readings
  6900. "diff_overrun_limit_&lt;diffLimit&gt;" und "less_data_in_period" </li> <br>
  6901. <li><b>errortext </b> - Grund eines Fehlerstatus </li> <br>
  6902. <li><b>background_processing_time </b> - die gesamte Prozesszeit die im Hintergrund/Blockingcall verbraucht wird </li> <br>
  6903. <li><b>diff_overrun_limit_&lt;diffLimit&gt;</b> - enthält eine Liste der Wertepaare die eine durch das Attribut "diffAccept" festgelegte Differenz
  6904. &lt;diffLimit&gt; (Standard: 20) überschreiten. Gilt für Funktion "diffValue". </li> <br>
  6905. <li><b>less_data_in_period </b> - enthält eine Liste der Zeitperioden in denen nur ein einziger Datensatz gefunden wurde. Die
  6906. Differenzberechnung berücksichtigt den letzten Wert der Vorperiode. Gilt für Funktion "diffValue". </li> <br>
  6907. <li><b>sql_processing_time </b> - der Anteil der Prozesszeit die für alle SQL-Statements der ausgeführten
  6908. Operation verbraucht wird </li> <br>
  6909. <li><b>SqlResult </b> - Ergebnis des letzten sqlCmd-Kommandos. Die Formatierung erfolgt entsprechend
  6910. des <a href="#DbRepattr">Attributes</a> "sqlResultFormat" </li> <br>
  6911. <li><b>sqlCmd </b> - das letzte ausgeführte sqlCmd-Kommando </li> <br>
  6912. </ul></ul>
  6913. <br>
  6914. </ul>
  6915. <a name="DbRepAutoRename"></a>
  6916. <b>DbRep Agent - automatisches Ändern von Device-Namen in Datenbanken und DbRep-Definitionen nach FHEM "rename" Kommando</b>
  6917. <br>
  6918. <ul>
  6919. Mit dem Attribut "role" wird die Rolle des DbRep-Device festgelegt. Die Standardrolle ist "Client". Mit der Änderung der Rolle in "Agent" wird das Device
  6920. veranlasst auf Umbenennungen von Geräten in der FHEM Installation zu reagieren. <br><br>
  6921. Durch den DbRep-Agenten werden folgende Features aktiviert wenn ein Gerät in FHEM mit "rename" umbenannt wird: <br><br>
  6922. <ul><ul>
  6923. <li> in der dem DbRep-Agenten zugeordneten Datenbank (Internal Database) wird nach Datensätzen mit dem alten Gerätenamen gesucht und dieser Gerätename in
  6924. <b>allen</b> betroffenen Datensätzen in den neuen Namen geändert. </li> <br>
  6925. <li> in dem DbRep-Agenten zugeordneten DbLog-Device wird in der Definition das alte durch das umbenannte Device ersetzt. Dadurch erfolgt ein weiteres Logging
  6926. des umbenannten Device in der Datenbank. </li> <br>
  6927. <li> in den existierenden DbRep-Definitionen vom Typ "Client" wird ein evtl. gesetztes Attribut "device = alter Devicename" in "device = neuer Devicename"
  6928. geändert. Dadurch werden Auswertungsdefinitionen bei Geräteumbenennungen automatisch konstistent gehalten. </li> <br>
  6929. </ul></ul>
  6930. Mit der Änderung in einen Agenten sind folgende Restriktionen verbunden die mit dem Setzen des Attributes "role = Agent" eingeschaltet
  6931. und geprüft werden: <br><br>
  6932. <ul><ul>
  6933. <li> es kann nur einen Agenten pro Datenbank in der FHEM-Installation geben. Ist mehr als eine Datenbank mit DbLog definiert, können
  6934. ebenso viele DbRep-Agenten eingerichtet werden </li> <br>
  6935. <li> mit der Umwandlung in einen Agenten wird nur noch das Set-Komando "renameDevice" verfügbar sein sowie nur ein eingeschränkter Satz von DbRep-spezifischen
  6936. Attributen zugelassen. Wird ein DbRep-Device vom bisherigen Typ "Client" in einen Agenten geändert, werden evtl. gesetzte und nun nicht mehr zugelassene
  6937. Attribute glöscht. </li> <br>
  6938. </ul></ul>
  6939. Die Aktivitäten wie Datenbankänderungen bzw. Änderungen an anderen DbRep-Definitionen werden im Logfile mit verbose=3 protokolliert. Damit die renameDevice-Funktion
  6940. bei großen Datenbanken nicht in ein timeout läuft, sollte das Attribut "timeout" entsprechend dimensioniert werden. Wie alle Datenbankoperationen des Moduls
  6941. wird auch das Autorename nonblocking ausgeführt. <br><br>
  6942. <ul>
  6943. <b>Beispiel </b> für die Definition eines DbRep-Device als Agent: <br><br>
  6944. <code>
  6945. define Rep.Agent DbRep LogDB <br>
  6946. attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen <br>
  6947. attr Rep.Agent icon security <br>
  6948. attr Rep.Agent role Agent <br>
  6949. attr Rep.Agent room DbLog <br>
  6950. attr Rep.Agent showproctime 1 <br>
  6951. attr Rep.Agent stateFormat { ReadingsVal("$name","state", undef) eq "running" ? "renaming" : ReadingsVal("$name","state", undef). " &raquo;; ProcTime: ".ReadingsVal("$name","sql_processing_time", undef)." sec"} <br>
  6952. attr Rep.Agent timeout 86400 <br>
  6953. </code>
  6954. <br>
  6955. </ul>
  6956. </ul>
  6957. =end html_DE
  6958. =cut