93_DbRep.pm 582 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933
  1. ##########################################################################################################
  2. # $Id: 93_DbRep.pm 16385 2018-03-11 17:45:36Z DS_Starter $
  3. ##########################################################################################################
  4. # 93_DbRep.pm
  5. #
  6. # (c) 2016-2018 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. # 7.14.4 11.03.2018 increased timeout of BlockingCall in DbRep_firstconnect
  41. # 7.14.3 07.03.2018 DbRep_firstconnect changed - get lowest timestamp in database, DbRep_Connect deleted
  42. # 7.14.2 04.03.2018 fix perl warning
  43. # 7.14.1 01.03.2018 currentfillup_Push bugfix for PostgreSQL
  44. # 7.14.0 26.02.2018 syncStandby
  45. # 7.13.3 25.02.2018 commandref revised (forum:#84953)
  46. # 7.13.2 24.02.2018 DbRep_firstconnect changed, bug fix in DbRep_collaggstr for aggregation = month
  47. # 7.13.1 20.02.2018 commandref revised
  48. # 7.13.0 17.02.2018 changeValue can handle perl code {} as "new string"
  49. # 7.12.0 16.02.2018 compression of dumpfile, restore of compressed files possible
  50. # 7.11.0 12.02.2018 new command "repairSQLite" to repair a corrupted SQLite database
  51. # 7.10.0 10.02.2018 bugfix delete attr timeYearPeriod if set other time attributes, new "changeValue" command
  52. # 7.9.0 09.02.2018 new attribute "avgTimeWeightMean" (time weight mean calculation), code review of selection
  53. # routines, maxValue handle negative values correctly,
  54. # one security second for correct create TimeArray in DbRep_normRelTime
  55. # 7.8.1 04.02.2018 bugfix if IsDisabled (again), code review, bugfix last dataset is not selected if timestamp
  56. # is fully set ("date time"), fix "$runtime_string_next = "$runtime_string_next.999";" if
  57. # $runtime_string_next is part of sql-execute place holder AND contains date+time
  58. # 7.8.0 04.02.2018 new command "eraseReadings"
  59. # 7.7.1 03.02.2018 minor fix in DbRep_firstconnect if IsDisabled
  60. # 7.7.0 29.01.2018 attribute "averageCalcForm", calculation sceme "avgDailyMeanGWS", "avgArithmeticMean" for
  61. # averageValue
  62. # 7.6.1 27.01.2018 new attribute "sqlCmdHistoryLength" and "fetchMarkDuplicates" for highlighting multiple
  63. # datasets by fetchrows
  64. # 7.6.0 26.01.2018 events containing "|" possible in fetchrows & delSeqDoublets, fetchrows displays multiple
  65. # $k entries with timestamp suffix $k (as index), sqlCmdHistory (avaiable if sqlCmd was
  66. # executed)
  67. # 7.5.5 25.01.2018 minor change in delSeqDoublets
  68. # 7.5.4 24.01.2018 delseqdoubl_DoParse reviewed to optimize memory usage, executeBeforeDump executeAfterDump
  69. # now available for "delSeqDoublets"
  70. # 7.5.3 23.01.2018 new attribute "ftpDumpFilesKeep", version management added to FTP-usage
  71. # 7.5.2 23.01.2018 fix typo DumpRowsCurrrent, dumpFilesKeep can be set to "0", commandref revised
  72. # 7.5.1 20.01.2018 DbRep_DumpDone changed to create background_processing_time before execute "executeAfterProc"
  73. # Commandref updated
  74. # 7.5.0 16.01.2018 DbRep_OutputWriteToDB, set options display/writeToDB for (max|min|sum|average|diff)Value
  75. # 7.4.1 14.01.2018 fix old dumpfiles not deleted by dumpMySQL clientSide
  76. # 7.4.0 09.01.2018 dumpSQLite/restoreSQLite,
  77. # backup/restore now available when DbLog-device has reopen xxxx running,
  78. # executeBeforeDump executeAfterDump also available for optimizeTables, vacuum, restoreMySQL,
  79. # restoreSQLite,
  80. # attribute executeBeforeDump / executeAfterDump renamed to executeBeforeProc & executeAfterProc
  81. # 7.3.1 08.01.2018 fix syntax error for perl < 5.20
  82. # 7.3.0 07.01.2018 DbRep-charfilter avoid control characters in datasets to export, impfile_Push errortext
  83. # improved,
  84. # expfile_DoParse changed to use aggregation for split selects in timeslices (avoid heavy
  85. # memory consumption)
  86. # 7.2.1 04.01.2018 bugfix month out of range that causes fhem crash
  87. # 7.2.0 27.12.2017 new attribute "seqDoubletsVariance"
  88. # 7.1.0 22.12.2017 new attribute timeYearPeriod for reports correspondig to e.g. electricity billing,
  89. # bugfix connection check is running after restart allthough dev is disabled
  90. # 7.0.0 18.12.2017 don't set $runtime_string_first,$runtime_string_next,$ts if time/aggregation-attributes
  91. # not set, change_Push redesigned, new command get blockinginfo, identify if reopen is
  92. # running on dblog-device and postpone the set-command
  93. # 6.4.3 17.12.2017 bugfix in delSeqDoublets, fetchrows if datasets contain characters like "' <blank> and s.o.
  94. # 6.4.2 15.12.2017 change "delSeqDoublets" to respect attribute "limit" (adviceDelete,adviceRemain),
  95. # commandref revised
  96. # 6.4.1 13.12.2017 new Attribute "sqlResultFieldSep" for field separate options of sqlCmd result
  97. # 6.4.0 10.12.2017 prepare module for usage of datetime picker widget (Forum:#35736)
  98. # 6.3.2 05.12.2017 make direction of fetchrows switchable ASC <-> DESC by attribute fetchRoute
  99. # 6.3.1 04.12.2017 fix DBD::mysql::st execute failed: Expression #1 of SELECT list is not in GROUP BY clause
  100. # and contains nonaggregated column 'DEVELfhem.history.TIMESTAMP' which is not functionally
  101. # dependent on columns in GROUP BY clause; this is incompatible with
  102. # sql_mode=only_full_group_by
  103. # Forum:https://forum.fhem.de/index.php/topic,65860.msg725595.html#msg725595 ,
  104. # fix currentfillup_Push PostgreSQL -> use $runtime_string_next as Timestring during current
  105. # insert
  106. # 6.3.0 04.12.2017 support addition format d:xx h:xx m:xx s:xx for attributes timeDiffToNow, timeOlderThan
  107. # 6.2.3 04.12.2017 fix localtime(time); (current time deduction) in DbRep_createTimeArray
  108. # 6.2.2 01.12.2017 support all aggregations for delSeqDoublets, better output filesize when mysql dump finished
  109. # 6.2.1 30.11.2017 support delSeqDoublets without device,reading is set and support device-devspec, reading list,
  110. # minor fixes in delSeqDoublets
  111. # 6.2.0 29.11.2017 enhanced command delSeqDoublets by "delete"
  112. # 6.1.0 29.11.2017 new command delSeqDoublets (adviceRemain,adviceDelete), add Option to LASTCMD
  113. # 6.0.0 18.11.2017 FTP transfer dumpfile after dump, delete old dumpfiles within Blockingcall (avoid freezes)
  114. # commandref revised, minor fixes
  115. # 5.8.6 30.10.2017 don't limit attr reading, device if the attr contains a list
  116. # 5.8.5 19.10.2017 filter unwanted characters in "procinfo"-result
  117. # 5.8.4 17.10.2017 DbRep_createSelectSql, DbRep_createDeleteSql, currentfillup_Push switch to devspec
  118. # 5.8.3 16.10.2017 change to use DbRep_createSelectSql: minValue,diffValue - DbRep_createDeleteSql: delEntries
  119. # 5.8.2 15.10.2017 sub DbRep_createTimeArray
  120. # 5.8.1 15.10.2017 change to use DbRep_createSelectSql: sumValue,averageValue,exportToFile,maxValue
  121. # 5.8.0 15.10.2017 adapt DbRep_createSelectSql for better performance if time/aggregation not set,
  122. # can set table as flexible argument for countEntries, fetchrows (default: history),
  123. # minor fixes
  124. # 5.7.1 13.10.2017 tableCurrentFillup fix for PostgreSQL, commandref revised
  125. # 5.7.0 09.10.2017 tableCurrentPurge, tableCurrentFillup
  126. # 5.6.4 05.10.2017 abortFn's adapted to use abortArg (Forum:77472)
  127. # 5.6.3 01.10.2017 fix crash of fhem due to wrong rmday-calculation if month is changed, Forum:#77328
  128. # 5.6.2 28.08.2017 commandref revised
  129. # 5.6.1 18.07.2017 commandref revised, minor fixes
  130. # 5.6.0 17.07.2017 default timeout changed to 86400, new get-command "procinfo" (MySQL)
  131. # 5.5.2 16.07.2017 dbmeta_DoParse -> show variables (no global)
  132. # 5.5.1 16.07.2017 wrong text output in state when restoreMySQL was aborted by timeout
  133. # 5.5.0 10.07.2017 replace $hash->{dbloghash}{DBMODEL} by $hash->{dbloghash}{MODEL} (DbLog was changed)
  134. # 5.4.0 03.07.2017 restoreMySQL - restore of csv-files (from dumpServerSide),
  135. # RestoreRowsHistory/ DumpRowsHistory, Commandref revised
  136. # 5.3.1 28.06.2017 vacuum for SQLite added, readings enhanced for optimizeTables / vacuum, commandref revised
  137. # 5.3.0 26.06.2017 change of DbRep_mysqlOptimizeTables, new command optimizeTables
  138. # 5.2.1 25.06.2017 bugfix in sqlCmd_DoParse (PRAGMA, UTF8, SHOW)
  139. # 5.2.0 14.06.2017 UTF-8 support for MySQL (fetchrows, srvinfo, expfile, impfile, insert)
  140. # 5.1.0 13.06.2017 column "UNIT" added to fetchrow result
  141. # 5.0.6 13.06.2017 add Aria engine to DbRep_mysqlOptimizeTables
  142. # 5.0.5 12.06.2017 bugfixes in DbRep_DumpAborted, some changes in dumpMySQL, optimizeTablesBeforeDump added to
  143. # mysql_DoDumpServerSide, new reading DumpFileCreatedSize
  144. # 5.0.4 09.06.2017 some improvements and changes of mysql_DoDump, commandref revised, new attributes
  145. # executeBeforeDump, executeAfterDump
  146. # 5.0.3 07.06.2017 mysql_DoDumpServerSide added
  147. # 5.0.2 06.06.2017 little improvements in mysql_DoDumpClientSide
  148. # 5.0.1 05.06.2017 dependencies between dumpMemlimit and dumpSpeed created, enhanced verbose 5 logging
  149. # 5.0.0 04.06.2017 MySQL Dump nonblocking added
  150. # 4.16.1 22.05.2017 encode json without JSON module, requires at least fhem.pl 14348 2017-05-22 20:25:06Z
  151. # 4.16.0 22.05.2017 format json as option of sqlResultFormat, state will never be deleted in "DbRep_delread"
  152. # 4.15.1 20.05.2017 correction of commandref
  153. # 4.15.0 17.05.2017 SUM(VALUE),AVG(VALUE) recreated for PostgreSQL, Code reviewed and optimized
  154. # 4.14.2 16.05.2017 SQL-Statements optimized for Wildcard "%" usage if used, Wildcard "_" isn't supported
  155. # furthermore, "averageValue", "sumValue", "maxValue", "minValue", "countEntries"
  156. # performance optimized,
  157. # commandref revised
  158. # 4.14.1 16.05.2017 limitation of fetchrows result datasets to 1000 by attr limit
  159. # 4.14.0 15.05.2017 UserExitFn added as separate sub (DbRep_userexit) and attr userExitFn defined,
  160. # new subs ReadingsBulkUpdateTimeState, ReadingsBulkUpdateValue,
  161. # ReadingsSingleUpdateValue, commandref revised
  162. # 4.13.7 11.05.2017 attribute sqlResultSingleFormat became sqlResultFormat, sqlResultSingle deleted and
  163. # sqlCmd contains now all format possibilities (separated,mline,sline,table),
  164. # commandref revised
  165. # 4.13.6 10.05.2017 minor changes
  166. # 4.13.5 09.05.2017 cover dbh prepare in eval to avoid crash (sqlResult_DoParse)
  167. # 4.13.4 09.05.2017 attribute sqlResultSingleFormat: mline sline table, attribute "allowDeletion" is now
  168. # also valid for sqlResult, sqlResultSingle and delete command is forced
  169. # 4.13.3 09.05.2017 flexible format of reading SqlResultRow_xxx for proper and sort sequence
  170. # 4.13.2 09.05.2017 sqlResult, sqlResultSingle are able to execute delete, insert, update commands
  171. # error corrections
  172. # 4.13.1 09.05.2017 change substitution in sqlResult, sqlResult_DoParse
  173. # 4.13.0 09.05.2017 acceptance of viegener change with some corrections (separating lines with ]|[ in Singleline)
  174. # viegener 07.05.2017 New sets sqlSelect execute arbitrary sql command returning each row as single reading (fields separated with |)
  175. # allowing replacement of timestamp values according to attribute definition --> §timestamp_begin§ etc
  176. # and sqlSelectSingle for executing an sql command returning a single reading (separating lines with §)
  177. # 4.12.2 17.04.2017 DbRep_checkUsePK changed
  178. # 4.12.1 07.04.2017 get tableinfo changed for MySQL
  179. # 4.12.0 31.03.2017 support of primary key for insert functions
  180. # 4.11.4 29.03.2017 bugfix timestamp in minValue, maxValue if VALUE contains more than one
  181. # numeric value (like in sysmon)
  182. # 4.11.3 26.03.2017 usage of daylight saving time changed to avoid wrong selection when wintertime
  183. # switch to summertime, minor bug fixes
  184. # 4.11.2 16.03.2017 bugfix in func dbmeta_DoParse (SQLITE_DB_FILENAME)
  185. # 4.11.1 28.02.2017 commandref completed
  186. # 4.11.0 18.02.2017 added [current|previous]_[month|week|day|hour]_begin and
  187. # [current|previous]_[month|week|day|hour]_end as options of timestamp
  188. # 4.10.3 01.02.2017 rename reading "diff-overrun_limit-" to "diff_overrun_limit_",
  189. # DbRep_collaggstr day aggregation changed back from 4.7.5 change
  190. # 4.10.2 16.01.2017 bugfix uninitialized value $renmode if RenameAgent
  191. # 4.10.1 30.11.2016 bugfix importFromFile format problem if UNIT-field wasn't set
  192. # 4.10 28.12.2016 del_DoParse changed to use Wildcards, del_ParseDone changed to use readingNameMap
  193. # 4.9 23.12.2016 function readingRename added
  194. # 4.8.6 17.12.2016 new bugfix group by-clause due to incompatible changes made in MyQL 5.7.5
  195. # (Forum #msg541103)
  196. # 4.8.5 16.12.2016 bugfix group by-clause due to Forum #msg540610
  197. # 4.8.4 13.12.2016 added "group by ...,table_schema" to select in dbmeta_DoParse due to Forum #msg539228,
  198. # commandref adapted, changed "not_enough_data_in_period" to "less_data_in_period"
  199. # 4.8.3 12.12.2016 balance diff to next period if value of period is 0 between two periods with
  200. # values
  201. # 4.8.2 10.12.2016 bugfix negativ diff if balanced
  202. # 4.8.1 10.12.2016 added balance diff to diffValue, a difference between the last value of an
  203. # old aggregation period to the first value of a new aggregation period will be take over now
  204. # 4.8 09.12.2016 diffValue selection chenged to "between"
  205. # 4.7.7 08.12.2016 code review
  206. # 4.7.6 07.12.2016 DbRep version as internal, check if perl module DBI is installed
  207. # 4.7.5 05.12.2016 DbRep_collaggstr day aggregation changed
  208. # 4.7.4 28.11.2016 sub DbRep_calcount changed due to Forum #msg529312
  209. # 4.7.3 20.11.2016 new diffValue function made suitable to SQLite
  210. # 4.7.2 20.11.2016 commandref adapted, state = Warnings adapted
  211. # 4.7.1 17.11.2016 changed fieldlength to DbLog new standard, diffValue state Warnings due to
  212. # several situations and generate readings not_enough_data_in_period, diff-overrun_limit
  213. # 4.7 16.11.2016 sub diffValue changed due to Forum #msg520154, attr diffAccept added,
  214. # diffValue now able to calculate if counter was going to 0
  215. # 4.6.1 01.11.2016 daylight saving time check improved
  216. # 4.6 31.10.2016 bugfix calc issue due to daylight saving time end (winter time)
  217. # 4.5.1 18.10.2016 get svrinfo contains SQLite database file size (MB),
  218. # modified timeout routine
  219. # 4.5 17.10.2016 get data of dbstatus, dbvars, tableinfo, svrinfo (database dependend)
  220. # 4.4 13.10.2016 get function prepared
  221. # 4.3 11.10.2016 Preparation of get metadata
  222. # 4.2 10.10.2016 allow SQL-Wildcards (% _) in attr reading & attr device
  223. # 4.1.3 09.10.2016 bugfix delEntries running on SQLite
  224. # 4.1.2 08.10.2016 old device in DEF of connected DbLog device will substitute by renamed device if
  225. # it is present in DEF
  226. # 4.1.1 06.10.2016 NotifyFn is getting events from global AND own device, set is reduced if
  227. # ROLE=Agent, english commandref enhanced
  228. # 4.1 05.10.2016 DbRep_Attr changed
  229. # 4.0 04.10.2016 Internal/Attribute ROLE added, sub DbRep_firstconnect changed
  230. # NotifyFN activated to start deviceRename if ROLE=Agent
  231. # 3.13 03.10.2016 added deviceRename to rename devices in database, new Internal DATABASE
  232. # 3.12 02.10.2016 function minValue added
  233. # 3.11.1 30.09.2016 bugfix include first and next day in calculation if Timestamp is exactly 'YYYY-MM-DD 00:00:00'
  234. # 3.11 29.09.2016 maxValue calculation moved to background to reduce FHEM-load
  235. # 3.10.1 28.09.2016 sub impFile -> changed $dbh->{AutoCommit} = 0 to $dbh->begin_work
  236. # 3.10 27.09.2016 diffValue calculation moved to background to reduce FHEM-load,
  237. # new reading background_processing_time
  238. # 3.9.1 27.09.2016 Internal "LASTCMD" added
  239. # 3.9 26.09.2016 new function importFromFile to import data from file (CSV format)
  240. # 3.8 16.09.2016 new attr readingPreventFromDel to prevent readings from deletion
  241. # when a new operation starts
  242. # 3.7.3 11.09.2016 changed format of diffValue-reading if no value was selected
  243. # 3.7.2 04.09.2016 problem in diffValue fixed if if no value was selected
  244. # 3.7.1 31.08.2016 Reading "errortext" added, commandref continued, exportToFile changed,
  245. # diffValue changed to fix wrong timestamp if error occur
  246. # 3.7 30.08.2016 exportToFile added (exports data to file (CSV format)
  247. # 3.6 29.08.2016 plausibility checks of database column character length
  248. # 3.5.2 21.08.2016 fit to new commandref style
  249. # 3.5.1 20.08.2016 commandref continued
  250. # 3.5 18.08.2016 new attribute timeOlderThan
  251. # 3.4.4 12.08.2016 current_year_begin, previous_year_begin, current_year_end, previous_year_end
  252. # added as possible values for timestmp attribute
  253. # 3.4.3 09.08.2016 fields for input using "insert" changed to "date,time,value,unit". Attributes
  254. # device, reading will be used to complete dataset,
  255. # now more informations available about faulty datasets in arithmetic operations
  256. # 3.4.2 05.08.2016 commandref complemented, fieldlength used in function "insert" trimmed to 32
  257. # 3.4.1 04.08.2016 check of numeric value type in functions maxvalue, diffvalue
  258. # 3.4 03.08.2016 function "insert" added
  259. # 3.3.3 16.07.2016 bugfix of aggregation=week if month start is 01 and month end is 12 AND
  260. # the last week of december is "01" like in 2014 (checked in version 11804)
  261. # 3.3.2 16.07.2016 readings completed with begin of selection range to ensure valid reading order,
  262. # also done if readingNameMap is set
  263. # 3.3.1 15.07.2016 function "diffValue" changed, write "-" if no value
  264. # 3.3 12.07.2016 function "diffValue" added
  265. # 3.2.1 12.07.2016 DbRep_Notify prepared, switched from readingsSingleUpdate to readingsBulkUpdate
  266. # 3.2 11.07.2016 handling of db-errors is relocated to blockingcall-subs (checked in version 11785)
  267. # 3.1.1 10.07.2016 state turns to initialized and connected after attr "disabled" is switched from "1" to "0"
  268. # 3.1 09.07.2016 new Attr "timeDiffToNow" and change subs according to that
  269. # 3.0 04.07.2016 no selection if timestamp isn't set and aggregation isn't set with fetchrows, delEntries
  270. # 2.9.9 03.07.2016 english version of commandref completed
  271. # 2.9.8 01.07.2016 changed fetchrows_ParseDone to handle readingvalues with whitespaces correctly
  272. # 2.9.7 30.06.2016 moved {DBLOGDEVICE} to {HELPER}{DBLOGDEVICE}
  273. # 2.9.6 30.06.2016 sql-call changed for countEntries, averageValue, sumValue avoiding
  274. # problems if no timestamp is set and aggregation is set
  275. # 2.9.5 30.06.2016 format of readingnames changed again (substitute ":" with "-" in time)
  276. # 2.9.4 30.06.2016 change readingmap to readingNameMap, prove of unsupported characters added
  277. # 2.9.3 27.06.2016 format of readingnames changed avoiding some problems after restart and splitting
  278. # 2.9.2 27.06.2016 use Time::Local added, DbRep_firstconnect added
  279. # 2.9.1 26.06.2016 german commandref added
  280. # 2.9 25.06.2016 attributes showproctime, timeout added
  281. # 2.8.1 24.06.2016 sql-creation of sumValue, maxValue, fetchrows changed
  282. # main-routine changed
  283. # 2.8 24.06.2016 function averageValue changed to nonblocking function
  284. # 2.7.1 24.06.2016 changed blockingcall routines, changed to unique abort-function
  285. # 2.7 23.06.2016 changed function countEntries to nonblocking
  286. # 2.6.3 22.06.2016 abort-routines changed, dbconnect-routines changed
  287. # 2.6.2 21.06.2016 aggregation week corrected
  288. # 2.6.1 20.06.2016 routine maxval_ParseDone corrected
  289. # 2.6 31.05.2016 maxValue changed to nonblocking function
  290. # 2.5.3 31.05.2016 function delEntries changed
  291. # 2.5.2 31.05.2016 ping check changed, DbRep_Connect changed
  292. # 2.5.1 30.05.2016 sleep in nb-functions deleted
  293. # 2.5 30.05.2016 changed to use own $dbh with DbLog-credentials, function sumValue, fetchrows
  294. # 2.4.2 29.05.2016 function sumValue changed
  295. # 2.4.1 29.05.2016 function fetchrow changed
  296. # 2.4 29.05.2016 changed to nonblocking function for sumValue
  297. # 2.3 28.05.2016 changed sumValue to "prepare" with placeholders
  298. # 2.2 27.05.2016 changed fetchrow and delEntries function to "prepare" with placeholders
  299. # added nonblocking function for delEntries
  300. # 2.1 25.05.2016 codechange
  301. # 2.0 24.05.2016 added nonblocking function for fetchrow
  302. # 1.2 21.05.2016 function and attribute for delEntries added
  303. # 1.1 20.05.2016 change result-format of "count", move runtime-counter to sub DbRep_collaggstr
  304. # 1.0 19.05.2016 Initial
  305. #
  306. package main;
  307. use strict;
  308. use warnings;
  309. use POSIX qw(strftime);
  310. use Time::HiRes qw(gettimeofday tv_interval);
  311. use Scalar::Util qw(looks_like_number);
  312. eval "use DBI;1" or my $DbRepMMDBI = "DBI";
  313. use DBI::Const::GetInfoType;
  314. use Blocking;
  315. use Color; # colorpicker Widget
  316. use Time::Local;
  317. use Encode qw(encode_utf8);
  318. use IO::Compress::Gzip qw(gzip $GzipError);
  319. use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  320. # no if $] >= 5.018000, warnings => 'experimental';
  321. no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  322. sub DbRep_Main($$;$);
  323. sub DbLog_cutCol($$$$$$$); # DbLog-Funktion nutzen um Daten auf maximale Länge beschneiden
  324. my $DbRepVersion = "7.14.4";
  325. my %dbrep_col = ("DEVICE" => 64,
  326. "TYPE" => 64,
  327. "EVENT" => 512,
  328. "READING" => 64,
  329. "VALUE" => 128,
  330. "UNIT" => 32
  331. );
  332. ###################################################################################
  333. # DbRep_Initialize
  334. ###################################################################################
  335. sub DbRep_Initialize($) {
  336. my ($hash) = @_;
  337. $hash->{DefFn} = "DbRep_Define";
  338. $hash->{UndefFn} = "DbRep_Undef";
  339. $hash->{ShutdownFn} = "DbRep_Shutdown";
  340. $hash->{NotifyFn} = "DbRep_Notify";
  341. $hash->{SetFn} = "DbRep_Set";
  342. $hash->{GetFn} = "DbRep_Get";
  343. $hash->{AttrFn} = "DbRep_Attr";
  344. $hash->{FW_deviceOverview} = 1;
  345. $hash->{AttrList} = "disable:1,0 ".
  346. "reading ".
  347. "allowDeletion:1,0 ".
  348. "averageCalcForm:avgArithmeticMean,avgDailyMeanGWS,avgTimeWeightMean ".
  349. "device " .
  350. "dumpComment ".
  351. "dumpCompress:1,0 ".
  352. "dumpDirLocal ".
  353. "dumpDirRemote ".
  354. "dumpMemlimit ".
  355. "dumpSpeed ".
  356. "dumpFilesKeep:0,1,2,3,4,5,6,7,8,9,10 ".
  357. "executeBeforeProc ".
  358. "executeAfterProc ".
  359. "expimpfile ".
  360. "fetchRoute:ascent,descent ".
  361. "fetchMarkDuplicates:red,blue,brown,green,orange ".
  362. "ftpDebug:1,0 ".
  363. "ftpDir ".
  364. "ftpDumpFilesKeep:1,2,3,4,5,6,7,8,9,10 ".
  365. "ftpPassive:1,0 ".
  366. "ftpPwd ".
  367. "ftpPort ".
  368. "ftpServer ".
  369. "ftpTimeout ".
  370. "ftpUse:1,0 ".
  371. "ftpUser ".
  372. "ftpUseSSL:1,0 ".
  373. "aggregation:hour,day,week,month,no ".
  374. "diffAccept ".
  375. "limit ".
  376. "optimizeTablesBeforeDump:1,0 ".
  377. "readingNameMap ".
  378. "readingPreventFromDel ".
  379. "role:Client,Agent ".
  380. "seqDoubletsVariance ".
  381. "showproctime:1,0 ".
  382. "showSvrInfo ".
  383. "showVariables ".
  384. "showStatus ".
  385. "showTableInfo ".
  386. "sqlCmdHistoryLength:0,5,10,15,20,25,30,35,40,45,50 ".
  387. "sqlResultFormat:separated,mline,sline,table,json ".
  388. "sqlResultFieldSep:|,:,\/ ".
  389. "timeYearPeriod ".
  390. "timestamp_begin ".
  391. "timestamp_end ".
  392. "timeDiffToNow ".
  393. "timeOlderThan ".
  394. "timeout ".
  395. "userExitFn ".
  396. $readingFnAttributes;
  397. return undef;
  398. }
  399. ###################################################################################
  400. # DbRep_Define
  401. ###################################################################################
  402. sub DbRep_Define($@) {
  403. # define <name> DbRep <DbLog-Device>
  404. # ($hash) [1] [2]
  405. #
  406. my ($hash, $def) = @_;
  407. my $name = $hash->{NAME};
  408. return "Error: Perl module ".$DbRepMMDBI." is missing. Install it on Debian with: sudo apt-get install libdbi-perl" if($DbRepMMDBI);
  409. my @a = split("[ \t][ \t]*", $def);
  410. if(int(@a) < 2) {
  411. return "You need to specify more parameters.\n". "Format: define <name> DbRep <DbLog-Device> <Reading> <Timestamp-Begin> <Timestamp-Ende>";
  412. }
  413. $hash->{LASTCMD} = " ";
  414. $hash->{ROLE} = AttrVal($name, "role", "Client");
  415. $hash->{HELPER}{DBLOGDEVICE} = $a[2];
  416. $hash->{VERSION} = $DbRepVersion;
  417. $hash->{NOTIFYDEV} = "global,".$name; # nur Events dieser Devices an DbRep_Notify weiterleiten
  418. my $dbconn = $defs{$a[2]}{dbconn};
  419. $hash->{DATABASE} = (split(/;|=/, $dbconn))[1];
  420. $hash->{UTF8} = defined($defs{$a[2]}{UTF8})?$defs{$a[2]}{UTF8}:0;
  421. my ($err,$hl) = DbRep_getCmdFile($name."_sqlCmdList");
  422. if(!$err) {
  423. $hash->{HELPER}{SQLHIST} = $hl;
  424. Log3 ($name, 4, "DbRep $name - history sql commandlist read from file ".$attr{global}{modpath}."/FHEM/FhemUtils/cacheDbRep");
  425. }
  426. RemoveInternalTimer($hash);
  427. InternalTimer(gettimeofday()+int(rand(45)), 'DbRep_firstconnect', $hash, 0);
  428. Log3 ($name, 4, "DbRep $name - initialized");
  429. ReadingsSingleUpdateValue ($hash, 'state', 'initialized', 1);
  430. return undef;
  431. }
  432. ###################################################################################
  433. # DbRep_Set
  434. ###################################################################################
  435. sub DbRep_Set($@) {
  436. my ($hash, @a) = @_;
  437. return "\"set X\" needs at least an argument" if ( @a < 2 );
  438. my $name = $a[0];
  439. my $opt = $a[1];
  440. my $prop = $a[2];
  441. my $dbh = $hash->{DBH};
  442. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  443. $hash->{dbloghash} = $defs{$dblogdevice};
  444. my $dbmodel = $hash->{dbloghash}{MODEL};
  445. my $dbname = $hash->{DATABASE};
  446. my $sd ="";
  447. my (@bkps,$dir);
  448. $dir = AttrVal($name, "dumpDirLocal", "./"); # 'dumpDirRemote' (Backup-Verz. auf dem MySQL-Server) muß gemountet sein und in 'dumpDirLocal' eingetragen sein
  449. $dir = $dir."/" unless($dir =~ m/\/$/);
  450. opendir(DIR,$dir);
  451. if ($dbmodel =~ /MYSQL/) {
  452. $dbname = $hash->{DATABASE};
  453. $sd = $dbname."_history_.*.csv";
  454. } elsif ($dbmodel =~ /SQLITE/) {
  455. $dbname = $hash->{DATABASE};
  456. $dbname = (split /[\/]/, $dbname)[-1];
  457. $dbname = (split /\./, $dbname)[0];
  458. $sd = $dbname."_.*.sqlitebkp";
  459. }
  460. while (my $file = readdir(DIR)) {
  461. next unless (-f "$dir/$file");
  462. next unless ($file =~ /^$sd/);
  463. push @bkps,$file;
  464. }
  465. closedir(DIR);
  466. my $cj = @bkps?join(",",reverse(sort @bkps)):" ";
  467. # Drop-Down Liste bisherige Befehle in "sqlCmd" erstellen
  468. my $hl = $hash->{HELPER}{SQLHIST}.",___purge_historylist___" if($hash->{HELPER}{SQLHIST});
  469. my $setlist = "Unknown argument $opt, choose one of ".
  470. "eraseReadings:noArg ".
  471. (($hash->{ROLE} ne "Agent")?"sumValue:display,writeToDB ":"").
  472. (($hash->{ROLE} ne "Agent")?"averageValue:display,writeToDB ":"").
  473. (($hash->{ROLE} ne "Agent")?"changeValue ":"").
  474. (($hash->{ROLE} ne "Agent")?"delEntries:noArg ":"").
  475. (($hash->{ROLE} ne "Agent")?"delSeqDoublets:adviceRemain,adviceDelete,delete ":"").
  476. "deviceRename ".
  477. (($hash->{ROLE} ne "Agent")?"readingRename ":"").
  478. (($hash->{ROLE} ne "Agent")?"exportToFile:noArg ":"").
  479. (($hash->{ROLE} ne "Agent")?"importFromFile:noArg ":"").
  480. (($hash->{ROLE} ne "Agent")?"maxValue:display,writeToDB ":"").
  481. (($hash->{ROLE} ne "Agent")?"minValue:display,writeToDB ":"").
  482. (($hash->{ROLE} ne "Agent")?"fetchrows:history,current ":"").
  483. (($hash->{ROLE} ne "Agent")?"diffValue:display,writeToDB ":"").
  484. (($hash->{ROLE} ne "Agent")?"insert ":"").
  485. (($hash->{ROLE} ne "Agent")?"sqlCmd ":"").
  486. (($hash->{ROLE} ne "Agent" && $hl)?"sqlCmdHistory:".$hl." ":"").
  487. (($hash->{ROLE} ne "Agent")?"syncStandby ":"").
  488. (($hash->{ROLE} ne "Agent")?"tableCurrentFillup:noArg ":"").
  489. (($hash->{ROLE} ne "Agent")?"tableCurrentPurge:noArg ":"").
  490. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"dumpMySQL:clientSide,serverSide ":"").
  491. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/ )?"dumpSQLite:noArg ":"").
  492. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/ )?"repairSQLite ":"").
  493. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/ )?"optimizeTables:noArg ":"").
  494. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE|POSTGRESQL/ )?"vacuum:noArg ":"").
  495. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/)?"restoreMySQL:".$cj." ":"").
  496. (($hash->{ROLE} ne "Agent" && $dbmodel =~ /SQLITE/)?"restoreSQLite:".$cj." ":"").
  497. (($hash->{ROLE} ne "Agent")?"countEntries:history,current ":"");
  498. return if(IsDisabled($name));
  499. if ($opt =~ /eraseReadings/) {
  500. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  501. # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  502. DbRep_delread($hash);
  503. return undef;
  504. }
  505. if ($opt eq "dumpMySQL" && $hash->{ROLE} ne "Agent") {
  506. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  507. if ($prop eq "serverSide") {
  508. Log3 ($name, 3, "DbRep $name - ################################################################");
  509. Log3 ($name, 3, "DbRep $name - ### New database serverSide dump ###");
  510. Log3 ($name, 3, "DbRep $name - ################################################################");
  511. } else {
  512. Log3 ($name, 3, "DbRep $name - ################################################################");
  513. Log3 ($name, 3, "DbRep $name - ### New database clientSide dump ###");
  514. Log3 ($name, 3, "DbRep $name - ################################################################");
  515. }
  516. # Befehl vor Procedure ausführen
  517. DbRep_beforeproc($hash, "dump");
  518. DbRep_Main($hash,$opt,$prop);
  519. return undef;
  520. }
  521. if ($opt eq "dumpSQLite" && $hash->{ROLE} ne "Agent") {
  522. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  523. Log3 ($name, 3, "DbRep $name - ################################################################");
  524. Log3 ($name, 3, "DbRep $name - ### New SQLite dump ###");
  525. Log3 ($name, 3, "DbRep $name - ################################################################");
  526. # Befehl vor Procedure ausführen
  527. DbRep_beforeproc($hash, "dump");
  528. DbRep_Main($hash,$opt,$prop);
  529. return undef;
  530. }
  531. if ($opt eq "repairSQLite" && $hash->{ROLE} ne "Agent") {
  532. $prop = $prop?$prop:36000;
  533. if($prop) {
  534. unless($prop =~ /^(\d+)$/) { return " The Value of $opt is not valid. Use only figures 0-9 without decimal places !";};
  535. # unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
  536. }
  537. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  538. Log3 ($name, 3, "DbRep $name - ################################################################");
  539. Log3 ($name, 3, "DbRep $name - ### New SQLite repair attempt ###");
  540. Log3 ($name, 3, "DbRep $name - ################################################################");
  541. Log3 ($name, 3, "DbRep $name - start repair attempt of database ".$hash->{DATABASE});
  542. # closetime Datenbank
  543. my $dbloghash = $hash->{dbloghash};
  544. my $dbl = $dbloghash->{NAME};
  545. CommandSet(undef,"$dbl reopen $prop");
  546. # Befehl vor Procedure ausführen
  547. DbRep_beforeproc($hash, "repair");
  548. DbRep_Main($hash,$opt);
  549. return undef;
  550. }
  551. if ($opt =~ /restoreMySQL|restoreSQLite/ && $hash->{ROLE} ne "Agent") {
  552. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  553. Log3 ($name, 3, "DbRep $name - ################################################################");
  554. Log3 ($name, 3, "DbRep $name - ### New database Restore/Recovery ###");
  555. Log3 ($name, 3, "DbRep $name - ################################################################");
  556. # Befehl vor Procedure ausführen
  557. DbRep_beforeproc($hash, "restore");
  558. DbRep_Main($hash,$opt,$prop);
  559. return undef;
  560. }
  561. if ($opt =~ /optimizeTables|vacuum/ && $hash->{ROLE} ne "Agent") {
  562. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  563. Log3 ($name, 3, "DbRep $name - ################################################################");
  564. Log3 ($name, 3, "DbRep $name - ### New optimize table / vacuum execution ###");
  565. Log3 ($name, 3, "DbRep $name - ################################################################");
  566. # Befehl vor Procedure ausführen
  567. DbRep_beforeproc($hash, "optimize");
  568. DbRep_Main($hash,$opt);
  569. return undef;
  570. }
  571. if ($opt =~ m/delSeqDoublets/ && $hash->{ROLE} ne "Agent") {
  572. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  573. if ($prop =~ /delete/ && !AttrVal($hash->{NAME}, "allowDeletion", 0)) {
  574. return " Set attribute 'allowDeletion' if you want to allow deletion of any database entries. Use it with care !";
  575. }
  576. DbRep_beforeproc($hash, "delSeq");
  577. DbRep_Main($hash,$opt,$prop);
  578. return undef;
  579. }
  580. if ($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) {
  581. $setlist = "Unknown argument $opt, choose one of ".
  582. (($hash->{ROLE} ne "Agent")?"cancelDump:noArg ":"");
  583. }
  584. if ($hash->{HELPER}{RUNNING_REPAIR}) {
  585. $setlist = "Unknown argument $opt, choose one of ".
  586. (($hash->{ROLE} ne "Agent")?"cancelRepair:noArg ":"");
  587. }
  588. if ($opt eq "cancelDump" && $hash->{ROLE} ne "Agent") {
  589. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  590. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  591. Log3 ($name, 3, "DbRep $name -> running Dump has been canceled");
  592. ReadingsSingleUpdateValue ($hash, "state", "Dump canceled", 1);
  593. return undef;
  594. }
  595. if ($opt eq "cancelRepair" && $hash->{ROLE} ne "Agent") {
  596. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  597. BlockingKill($hash->{HELPER}{RUNNING_REPAIR});
  598. Log3 ($name, 3, "DbRep $name -> running Repair has been canceled");
  599. ReadingsSingleUpdateValue ($hash, "state", "Repair canceled", 1);
  600. return undef;
  601. }
  602. #######################################################################################################
  603. ## keine Aktionen außer die über diesem Eintrag solange Reopen xxxx im DbLog-Device läuft
  604. #######################################################################################################
  605. if ($hash->{dbloghash}{HELPER}{REOPEN_RUNS} && $opt !~ /\?/) {
  606. my $ro = (split(" ",FmtDateTime(gettimeofday()+$hash->{dbloghash}{HELPER}{REOPEN_RUNS})))[1];
  607. Log3 ($name, 3, "DbRep $name - connection $dblogdevice to db $dbname is closed until $ro - $opt postponed");
  608. ReadingsSingleUpdateValue ($hash, "state", "connection $dblogdevice to $dbname is closed until $ro - $opt postponed", 1);
  609. return;
  610. }
  611. #######################################################################################################
  612. if ($opt =~ /countEntries/ && $hash->{ROLE} ne "Agent") {
  613. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  614. my $table = $prop?$prop:"history";
  615. DbRep_Main($hash,$opt,$table);
  616. } elsif ($opt =~ /fetchrows/ && $hash->{ROLE} ne "Agent") {
  617. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  618. my $table = $prop?$prop:"history";
  619. DbRep_Main($hash,$opt,$table);
  620. } elsif ($opt =~ m/(max|min|sum|average|diff)Value/ && $hash->{ROLE} ne "Agent") {
  621. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  622. if (!AttrVal($hash->{NAME}, "reading", "")) {
  623. return " The attribute reading to analyze is not set !";
  624. }
  625. if ($prop && $prop =~ /writeToDB/) {
  626. if (!AttrVal($hash->{NAME}, "device", "") || AttrVal($hash->{NAME}, "device", "") =~ /[%.*:=,]/ || AttrVal($hash->{NAME}, "reading", "") =~ /[,\s]/) {
  627. return "<html>If you want write results back to database, attributes \"device\" and \"reading\" must be set.<br>
  628. In that case \"device\" mustn't be a <a href='https://fhem.de/commandref.html#devspec\'>devspec</a> and mustn't contain SQL-Wildcard (%).<br>
  629. The \"reading\" to evaluate has to be a single reading and no list.</html>";
  630. }
  631. }
  632. DbRep_Main($hash,$opt,$prop);
  633. } elsif ($opt =~ m/delEntries|tableCurrentPurge/ && $hash->{ROLE} ne "Agent") {
  634. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  635. if (!AttrVal($hash->{NAME}, "allowDeletion", undef)) {
  636. return " Set attribute 'allowDeletion' if you want to allow deletion of any database entries. Use it with care !";
  637. }
  638. DbRep_Main($hash,$opt);
  639. } elsif ($opt =~ m/tableCurrentFillup/ && $hash->{ROLE} ne "Agent") {
  640. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  641. DbRep_Main($hash,$opt);
  642. } elsif ($opt eq "deviceRename") {
  643. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  644. my ($olddev, $newdev) = split(",",$prop);
  645. if (!$olddev || !$newdev) {return "Both entries \"old device name\", \"new device name\" are needed. Use \"set $name deviceRename olddevname,newdevname\" ";}
  646. $hash->{HELPER}{OLDDEV} = $olddev;
  647. $hash->{HELPER}{NEWDEV} = $newdev;
  648. $hash->{HELPER}{RENMODE} = "devren";
  649. DbRep_Main($hash,$opt);
  650. } elsif ($opt eq "readingRename") {
  651. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  652. my ($oldread, $newread) = split(",",$prop);
  653. if (!$oldread || !$newread) {return "Both entries \"old reading name\", \"new reading name\" are needed. Use \"set $name readingRename oldreadingname,newreadingname\" ";}
  654. $hash->{HELPER}{OLDREAD} = $oldread;
  655. $hash->{HELPER}{NEWREAD} = $newread;
  656. $hash->{HELPER}{RENMODE} = "readren";
  657. DbRep_Main($hash,$opt);
  658. } elsif ($opt eq "insert" && $hash->{ROLE} ne "Agent") {
  659. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  660. if ($prop) {
  661. if (!AttrVal($hash->{NAME}, "device", "") || !AttrVal($hash->{NAME}, "reading", "") ) {
  662. return "One or both of attributes \"device\", \"reading\" are not set. It's mandatory to set both to complete dataset for manual insert !";
  663. }
  664. # Attribute device & reading dürfen kein SQL-Wildcard % enthalten
  665. return "One or both of attributes \"device\", \"reading\" containing SQL wildcard \"%\". Wildcards are not allowed in function manual insert !"
  666. if(AttrVal($hash->{NAME},"device","") =~ m/%/ || AttrVal($hash->{NAME},"reading","") =~ m/%/ );
  667. my ($i_date, $i_time, $i_value, $i_unit) = split(",",$prop);
  668. 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)>' ";}
  669. unless ($i_date =~ /(\d{4})-(\d{2})-(\d{2})/) {return "Input for date is not valid. Use format YYYY-MM-DD !";}
  670. unless ($i_time =~ /(\d{2}):(\d{2}):(\d{2})/) {return "Input for time is not valid. Use format HH:MM:SS !";}
  671. my $i_timestamp = $i_date." ".$i_time;
  672. my ($yyyy, $mm, $dd, $hh, $min, $sec) = ($i_timestamp =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  673. eval { my $ts = timelocal($sec, $min, $hh, $dd, $mm-1, $yyyy-1900); };
  674. if ($@) {
  675. my @l = split (/at/, $@);
  676. return " Timestamp is out of range - $l[0]";
  677. }
  678. my $i_device = AttrVal($hash->{NAME}, "device", "");
  679. my $i_reading = AttrVal($hash->{NAME}, "reading", "");
  680. # Daten auf maximale Länge (entsprechend der Feldlänge in DbLog DB create-scripts) beschneiden wenn nicht SQLite
  681. if ($dbmodel ne 'SQLITE') {
  682. $i_device = substr($i_device,0, $dbrep_col{DEVICE});
  683. $i_reading = substr($i_reading,0, $dbrep_col{READING});
  684. $i_value = substr($i_value,0, $dbrep_col{VALUE});
  685. $i_unit = substr($i_unit,0, $dbrep_col{UNIT}) if($i_unit);
  686. }
  687. $hash->{HELPER}{I_TIMESTAMP} = $i_timestamp;
  688. $hash->{HELPER}{I_DEVICE} = $i_device;
  689. $hash->{HELPER}{I_READING} = $i_reading;
  690. $hash->{HELPER}{I_VALUE} = $i_value;
  691. $hash->{HELPER}{I_UNIT} = $i_unit;
  692. $hash->{HELPER}{I_TYPE} = my $i_type = "manual";
  693. $hash->{HELPER}{I_EVENT} = my $i_event = "manual";
  694. } else {
  695. return "Data to insert to table 'history' are needed like this pattern: 'Date,Time,Value,[Unit]'. \"Unit\" is optional. Spaces are not allowed !";
  696. }
  697. DbRep_Main($hash,$opt);
  698. } elsif ($opt eq "exportToFile" && $hash->{ROLE} ne "Agent") {
  699. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  700. if (!AttrVal($hash->{NAME}, "expimpfile", "")) {
  701. return "The attribute \"expimpfile\" (path and filename) has to be set for export to file !";
  702. }
  703. DbRep_Main($hash,$opt);
  704. } elsif ($opt eq "importFromFile" && $hash->{ROLE} ne "Agent") {
  705. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  706. if (!AttrVal($hash->{NAME}, "expimpfile", "")) {
  707. return "The attribute \"expimpfile\" (path and filename) has to be set for import from file !";
  708. }
  709. DbRep_Main($hash,$opt);
  710. } elsif ($opt =~ /sqlCmd|sqlCmdHistory/) {
  711. return "\"set $opt\" needs at least an argument" if ( @a < 3 );
  712. # remove arg 0, 1 to get SQL command
  713. my $sqlcmd;
  714. if($opt eq "sqlCmd") {
  715. shift @a;
  716. shift @a;
  717. $sqlcmd = join(" ", @a);
  718. }
  719. if($opt eq "sqlCmdHistory") {
  720. $prop =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\\]^_`{|}~äöüÄÖÜ߀/ /cs;
  721. $prop =~ s/<c>/,/g;
  722. $sqlcmd = $prop;
  723. if($sqlcmd eq "___purge_historylist___") {
  724. delete($hash->{HELPER}{SQLHIST});
  725. DbRep_setCmdFile($name."_sqlCmdList","",$hash); # Löschen der sql History Liste im DbRep-Keyfile
  726. return "SQL command historylist of $name deleted.";
  727. }
  728. }
  729. $hash->{LASTCMD} = $sqlcmd?"$opt $sqlcmd":"$opt";
  730. if ($sqlcmd =~ m/^\s*delete/is && !AttrVal($hash->{NAME}, "allowDeletion", undef)) {
  731. return " Attribute 'allowDeletion = 1' is needed for command '$sqlcmd'. Use it with care !";
  732. }
  733. DbRep_Main($hash,$opt,$sqlcmd);
  734. } elsif ($opt =~ /changeValue/) {
  735. shift @a;
  736. shift @a;
  737. $prop = join(" ", @a);
  738. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  739. unless($prop =~ m/^\s*(".*",".*")\s*$/) {return "Both entries \"old string\", \"new string\" are needed. Use \"set $name changeValue \"old string\",\"new string\" (use quotes)";}
  740. my $complex = 0;
  741. my ($oldval,$newval) = ($prop =~ /^\s*"(.*?)","(.*?)"\s*$/);
  742. if($newval =~ m/[{}]/) {
  743. if($newval =~ m/^\s*(\{.*\})\s*$/s) {
  744. $newval = $1;
  745. $complex = 1;
  746. my %specials = (
  747. "%VALUE" => $name,
  748. "%UNIT" => $name,
  749. );
  750. $newval = EvalSpecials($newval, %specials);
  751. } else {
  752. return "The expression of \"new string\" has to be included in \"{ }\" ";
  753. }
  754. }
  755. $hash->{HELPER}{COMPLEX} = $complex;
  756. $hash->{HELPER}{OLDVAL} = $oldval;
  757. $hash->{HELPER}{NEWVAL} = $newval;
  758. $hash->{HELPER}{RENMODE} = "changeval";
  759. DbRep_beforeproc($hash, "changeval");
  760. DbRep_Main($hash,$opt);
  761. } elsif ($opt =~ m/syncStandby/ && $hash->{ROLE} ne "Agent") {
  762. unless($prop) {return "A DbLog-device (standby) is needed to sync. Use \"set $name syncStandby <DbLog-standby name>\" ";}
  763. if(!exists($defs{$prop}) || $defs{$prop}->{TYPE} ne "DbLog") {
  764. return "The device \"$prop\" doesn't exist or is not a DbLog-device. ";
  765. }
  766. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  767. DbRep_Main($hash,$opt,$prop);
  768. } else {
  769. return "$setlist";
  770. }
  771. return undef;
  772. }
  773. ###################################################################################
  774. # DbRep_Get
  775. ###################################################################################
  776. sub DbRep_Get($@) {
  777. my ($hash, @a) = @_;
  778. return "\"get X\" needs at least an argument" if ( @a < 2 );
  779. my $name = $a[0];
  780. my $opt = $a[1];
  781. my $prop = $a[2];
  782. my $dbh = $hash->{DBH};
  783. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  784. $hash->{dbloghash} = $defs{$dblogdevice};
  785. my $dbmodel = $hash->{dbloghash}{MODEL};
  786. my $dbname = $hash->{DATABASE};
  787. my $to = AttrVal($name, "timeout", "86400");
  788. my $getlist = "Unknown argument $opt, choose one of ".
  789. "svrinfo:noArg ".
  790. "blockinginfo:noArg ".
  791. "minTimestamp:noArg ".
  792. (($dbmodel eq "MYSQL")?"dbstatus:noArg ":"").
  793. (($dbmodel eq "MYSQL")?"tableinfo:noArg ":"").
  794. (($dbmodel eq "MYSQL")?"procinfo:noArg ":"").
  795. (($dbmodel eq "MYSQL")?"dbvars:noArg ":"")
  796. ;
  797. return if(IsDisabled($name));
  798. if ($hash->{dbloghash}{HELPER}{REOPEN_RUNS} && $opt !~ /\?|procinfo|blockinginfo/) {
  799. my $ro = (split(" ",FmtDateTime(gettimeofday()+$hash->{dbloghash}{HELPER}{REOPEN_RUNS})))[1];
  800. Log3 ($name, 3, "DbRep $name - connection $dblogdevice to db $dbname is closed until $ro - $opt postponed");
  801. ReadingsSingleUpdateValue ($hash, "state", "connection $dblogdevice to $dbname is closed until $ro - $opt postponed", 1);
  802. return;
  803. }
  804. if ($opt =~ /dbvars|dbstatus|tableinfo|procinfo/) {
  805. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  806. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  807. return "The operation \"$opt\" isn't available with database type $dbmodel" if ($dbmodel ne 'MYSQL');
  808. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  809. DbRep_delread($hash); # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  810. $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "DbRep_ParseAborted", $hash);
  811. } elsif ($opt eq "svrinfo") {
  812. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  813. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  814. DbRep_delread($hash);
  815. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  816. $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "DbRep_ParseAborted", $hash);
  817. } elsif ($opt eq "blockinginfo") {
  818. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  819. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  820. DbRep_delread($hash);
  821. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  822. DbRep_getblockinginfo($hash);
  823. } elsif ($opt eq "minTimestamp") {
  824. return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  825. $hash->{LASTCMD} = $prop?"$opt $prop":"$opt";
  826. DbRep_delread($hash);
  827. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  828. DbRep_firstconnect($hash);
  829. } else {
  830. return "$getlist";
  831. }
  832. return undef;
  833. }
  834. ###################################################################################
  835. # DbRep_Attr
  836. ###################################################################################
  837. sub DbRep_Attr($$$$) {
  838. my ($cmd,$name,$aName,$aVal) = @_;
  839. my $hash = $defs{$name};
  840. $hash->{dbloghash} = $defs{$hash->{HELPER}{DBLOGDEVICE}};
  841. my $dbmodel = $hash->{dbloghash}{MODEL};
  842. my $do;
  843. # $cmd can be "del" or "set"
  844. # $name is device name
  845. # aName and aVal are Attribute name and value
  846. # nicht erlaubte / nicht setzbare Attribute wenn role = Agent
  847. my @agentnoattr = qw(aggregation
  848. allowDeletion
  849. dumpDirLocal
  850. reading
  851. readingNameMap
  852. readingPreventFromDel
  853. device
  854. diffAccept
  855. executeBeforeProc
  856. executeAfterProc
  857. expimpfile
  858. ftpUse
  859. ftpUser
  860. ftpUseSSL
  861. ftpDebug
  862. ftpDir
  863. ftpPassive
  864. ftpPort
  865. ftpPwd
  866. ftpServer
  867. ftpTimeout
  868. dumpMemlimit
  869. dumpComment
  870. dumpSpeed
  871. optimizeTablesBeforeDump
  872. seqDoubletsVariance
  873. sqlCmdHistoryLength
  874. timeYearPeriod
  875. timestamp_begin
  876. timestamp_end
  877. timeDiffToNow
  878. timeOlderThan
  879. sqlResultFormat
  880. );
  881. if ($aName eq "disable") {
  882. if($cmd eq "set") {
  883. $do = ($aVal) ? 1 : 0;
  884. }
  885. $do = 0 if($cmd eq "del");
  886. my $val = ($do == 1 ? "disabled" : "initialized");
  887. ReadingsSingleUpdateValue ($hash, "state", $val, 1);
  888. if ($do == 0) {
  889. RemoveInternalTimer($hash);
  890. InternalTimer(time+5, 'DbRep_firstconnect', $hash, 0);
  891. } else {
  892. my $dbh = $hash->{DBH};
  893. $dbh->disconnect() if($dbh);
  894. }
  895. }
  896. if ($cmd eq "set" && $hash->{ROLE} eq "Agent") {
  897. foreach (@agentnoattr) {
  898. return ("Attribute $aName is not usable due to role of $name is \"$hash->{ROLE}\" ") if ($_ eq $aName);
  899. }
  900. }
  901. if ($aName eq "readingPreventFromDel") {
  902. if($cmd eq "set") {
  903. 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.";}
  904. $hash->{HELPER}{RDPFDEL} = $aVal;
  905. } else {
  906. delete $hash->{HELPER}{RDPFDEL} if($hash->{HELPER}{RDPFDEL});
  907. }
  908. }
  909. if ($aName eq "sqlCmdHistoryLength") {
  910. if($cmd eq "set") {
  911. $do = ($aVal) ? 1 : 0;
  912. }
  913. $do = 0 if($cmd eq "del");
  914. if ($do == 0) {
  915. delete($hash->{HELPER}{SQLHIST});
  916. DbRep_setCmdFile($name."_sqlCmdList","",$hash); # Löschen der sql History Liste im DbRep-Keyfile
  917. }
  918. }
  919. if ($aName eq "userExitFn") {
  920. if($cmd eq "set") {
  921. if(!$aVal) {return "Usage of $aName is wrong. The function has to be specified as \"<UserExitFn> [reading:value]\" ";}
  922. my @a = split(/ /,$aVal,2);
  923. $hash->{HELPER}{USEREXITFN} = $a[0];
  924. $hash->{HELPER}{UEFN_REGEXP} = $a[1] if($a[1]);
  925. } else {
  926. delete $hash->{HELPER}{USEREXITFN} if($hash->{HELPER}{USEREXITFN});
  927. delete $hash->{HELPER}{UEFN_REGEXP} if($hash->{HELPER}{UEFN_REGEXP});
  928. }
  929. }
  930. if ($aName eq "role") {
  931. if($cmd eq "set") {
  932. if ($aVal eq "Agent") {
  933. # check ob bereits ein Agent für die angeschlossene Datenbank existiert -> DbRep-Device kann dann keine Agent-Rolle einnehmen
  934. foreach(devspec2array("TYPE=DbRep")) {
  935. my $devname = $_;
  936. next if($devname eq $name);
  937. my $devrole = $defs{$_}{ROLE};
  938. my $devdb = $defs{$_}{DATABASE};
  939. if ($devrole eq "Agent" && $devdb eq $hash->{DATABASE}) { return "There is already an Agent device: $devname defined for database $hash->{DATABASE} !"; }
  940. }
  941. # nicht erlaubte Attribute löschen falls gesetzt
  942. foreach (@agentnoattr) {
  943. delete($attr{$name}{$_});
  944. }
  945. $attr{$name}{icon} = "security";
  946. }
  947. $do = $aVal;
  948. } else {
  949. $do = "Client";
  950. }
  951. $hash->{ROLE} = $do;
  952. delete($attr{$name}{icon}) if($do eq "Client");
  953. }
  954. if ($cmd eq "set") {
  955. if ($aName =~ /seqDoubletsVariance/) {
  956. unless (looks_like_number($aVal)) { return " The Value of $aName is not valid. Only figures are allowed !";}
  957. }
  958. if ($aName eq "timeYearPeriod") {
  959. # 06-01 02-28
  960. unless ($aVal =~ /^(\d{2})-(\d{2}) (\d{2})-(\d{2})$/ )
  961. { return "The Value of \"$aName\" isn't valid. Set the account period as \"MM-DD MM-DD\".";}
  962. my ($mm1, $dd1, $mm2, $dd2) = ($aVal =~ /^(\d{2})-(\d{2}) (\d{2})-(\d{2})$/);
  963. my (undef,undef,undef,$mday,$mon,$year1,undef,undef,undef) = localtime(time); # Istzeit Ableitung
  964. my $year2 = $year1;
  965. # a b c d
  966. # 06-01 02-28 , wenn c < a && $mon < a -> Jahr(a)-1, sonst Jahr(c)+1
  967. my $c = ($mon+1).$mday;
  968. my $e = $mm2.$dd2;
  969. if ($mm2 <= $mm1 && $c <= $e) {
  970. $year1--;
  971. } else {
  972. $year2++;
  973. }
  974. eval { my $t1 = timelocal(00, 00, 00, $dd1, $mm1-1, $year1-1900);
  975. my $t2 = timelocal(00, 00, 00, $dd2, $mm2-1, $year2-1900); };
  976. if ($@) {
  977. my @l = split (/at/, $@);
  978. return " The Value of $aName is out of range - $l[0]";
  979. }
  980. delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
  981. delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
  982. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  983. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  984. return undef;
  985. }
  986. if ($aName eq "timestamp_begin" || $aName eq "timestamp_end") {
  987. my ($a,$b,$c) = split('_',$aVal);
  988. if ($a =~ /^current$|^previous$/ && $b =~ /^hour$|^day$|^week$|^month$|^year$/ && $c =~ /^begin$|^end$/) {
  989. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  990. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  991. return undef;
  992. }
  993. $aVal = DbRep_formatpicker($aVal);
  994. unless ($aVal =~ /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/)
  995. {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\" !";}
  996. my ($yyyy, $mm, $dd, $hh, $min, $sec) = ($aVal =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  997. eval { my $epoch_seconds_begin = timelocal($sec, $min, $hh, $dd, $mm-1, $yyyy-1900); };
  998. if ($@) {
  999. my @l = split (/at/, $@);
  1000. return " The Value of $aName is out of range - $l[0]";
  1001. }
  1002. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  1003. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  1004. }
  1005. if ($aName =~ /ftpTimeout|timeout|diffAccept/) {
  1006. unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 without decimal places !";}
  1007. }
  1008. if ($aName eq "readingNameMap") {
  1009. unless ($aVal =~ m/^[A-Za-z\d_\.-]+$/) { return " Unsupported character in $aName found. Use only A-Z a-z _ . -";}
  1010. }
  1011. if ($aName eq "timeDiffToNow") {
  1012. unless ($aVal =~ /^[0-9]+$/ || $aVal =~ /^\s*[dhms]:([\d]+)\s*/ && $aVal !~ /.*,.*/ )
  1013. { return "The Value of \"$aName\" isn't valid. Set simple seconds like \"86400\" or use form like \"d:10 h:6 m:12 s:20\". Refer to commandref !";}
  1014. delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
  1015. delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
  1016. delete($attr{$name}{timeOlderThan}) if ($attr{$name}{timeOlderThan});
  1017. delete($attr{$name}{timeYearPeriod}) if ($attr{$name}{timeYearPeriod});
  1018. }
  1019. if ($aName eq "timeOlderThan") {
  1020. unless ($aVal =~ /^[0-9]+$/ || $aVal =~ /^\s*[dhms]:([\d]+)\s*/ && $aVal !~ /.*,.*/ )
  1021. { return "The Value of \"$aName\" isn't valid. Set simple seconds like \"86400\" or use form like \"d:10 h:6 m:12 s:20\". Refer to commandref !";}
  1022. delete($attr{$name}{timestamp_begin}) if ($attr{$name}{timestamp_begin});
  1023. delete($attr{$name}{timestamp_end}) if ($attr{$name}{timestamp_end});
  1024. delete($attr{$name}{timeDiffToNow}) if ($attr{$name}{timeDiffToNow});
  1025. delete($attr{$name}{timeYearPeriod}) if ($attr{$name}{timeYearPeriod});
  1026. }
  1027. if ($aName eq "dumpMemlimit" || $aName eq "dumpSpeed") {
  1028. unless ($aVal =~ /^[0-9]+$/) { return "The Value of $aName is not valid. Use only figures 0-9 without decimal places.";}
  1029. my $dml = AttrVal($name, "dumpMemlimit", 100000);
  1030. my $ds = AttrVal($name, "dumpSpeed", 10000);
  1031. if($aName eq "dumpMemlimit") {
  1032. unless($aVal >= (10 * $ds)) {return "The Value of $aName has to be at least '10 x dumpSpeed' ! ";}
  1033. }
  1034. if($aName eq "dumpSpeed") {
  1035. unless($aVal <= ($dml / 10)) {return "The Value of $aName mustn't be greater than 'dumpMemlimit / 10' ! ";}
  1036. }
  1037. }
  1038. if ($aName eq "ftpUse") {
  1039. delete($attr{$name}{ftpUseSSL});
  1040. }
  1041. if ($aName eq "ftpUseSSL") {
  1042. delete($attr{$name}{ftpUse});
  1043. }
  1044. if ($aName eq "reading" || $aName eq "device") {
  1045. if ($dbmodel && $dbmodel ne 'SQLITE') {
  1046. my $attrname = uc($aName);
  1047. if ($dbmodel eq 'POSTGRESQL' && $aVal !~ m/,/) {
  1048. return "Length of \"$aName\" is too big. Maximum length for database type $dbmodel is $dbrep_col{$attrname}" if(length($aVal) > $dbrep_col{$attrname});
  1049. } elsif ($dbmodel eq 'MYSQL' && $aVal !~ m/,/) {
  1050. return "Length of \"$aName\" is too big. Maximum length for database type $dbmodel is $dbrep_col{$attrname}" if(length($aVal) > $dbrep_col{$attrname});
  1051. }
  1052. }
  1053. }
  1054. }
  1055. return undef;
  1056. }
  1057. ###################################################################################
  1058. # DbRep_Notify Eventverarbeitung
  1059. ###################################################################################
  1060. sub DbRep_Notify($$) {
  1061. # Es werden nur die Events von Geräten verarbeitet die im Hash $hash->{NOTIFYDEV} gelistet sind (wenn definiert).
  1062. # Dadurch kann die Menge der Events verringert werden. In sub DbRep_Define angeben.
  1063. # Beispiele:
  1064. # $hash->{NOTIFYDEV} = "global";
  1065. # $hash->{NOTIFYDEV} = "global,Definition_A,Definition_B";
  1066. my ($own_hash, $dev_hash) = @_;
  1067. my $myName = $own_hash->{NAME}; # Name des eigenen Devices
  1068. my $devName = $dev_hash->{NAME}; # Device welches Events erzeugt hat
  1069. return if(IsDisabled($myName)); # Return if the module is disabled
  1070. my $events = deviceEvents($dev_hash,0);
  1071. return if(!$events);
  1072. foreach my $event (@{$events}) {
  1073. $event = "" if(!defined($event));
  1074. my @evl = split("[ \t][ \t]*", $event);
  1075. # if ($devName = $myName && $evl[0] =~ /done/) {
  1076. # InternalTimer(time+1, "browser_refresh", $own_hash, 0);
  1077. # }
  1078. if ($own_hash->{ROLE} eq "Agent") {
  1079. # wenn Rolle "Agent" Verbeitung von RENAMED Events
  1080. next if ($event !~ /RENAMED/);
  1081. my $strucChanged;
  1082. # altes in neues device in der DEF des angeschlossenen DbLog-device ändern (neues device loggen)
  1083. my $dblog_name = $own_hash->{dbloghash}{NAME}; # Name des an den DbRep-Agenten angeschlossenen DbLog-Dev
  1084. my $dblog_hash = $defs{$dblog_name};
  1085. if ( $dblog_hash->{DEF} =~ m/( |\(|\|)$evl[1]( |\)|\||:)/ ) {
  1086. $dblog_hash->{DEF} =~ s/$evl[1]/$evl[2]/;
  1087. $dblog_hash->{REGEXP} =~ s/$evl[1]/$evl[2]/;
  1088. # Definitionsänderung wurde vorgenommen
  1089. $strucChanged = 1;
  1090. Log3 ($myName, 3, "DbRep Agent $myName - $dblog_name substituted in DEF, old: \"$evl[1]\", new: \"$evl[2]\" ");
  1091. }
  1092. # DEVICE innerhalb angeschlossener Datenbank umbenennen
  1093. Log3 ($myName, 4, "DbRep Agent $myName - Evt RENAMED rec - old device: $evl[1], new device: $evl[2] -> start deviceRename in DB: $own_hash->{DATABASE} ");
  1094. $own_hash->{HELPER}{OLDDEV} = $evl[1];
  1095. $own_hash->{HELPER}{NEWDEV} = $evl[2];
  1096. $own_hash->{HELPER}{RENMODE} = "devren";
  1097. DbRep_Main($own_hash,"deviceRename");
  1098. # die Attribute "device" in allen DbRep-Devices mit der Datenbank = DB des Agenten von alten Device in neues Device ändern
  1099. foreach(devspec2array("TYPE=DbRep")) {
  1100. my $repname = $_;
  1101. next if($_ eq $myName);
  1102. my $repattrdevice = $attr{$_}{device};
  1103. next if(!$repattrdevice);
  1104. my $repdb = $defs{$_}{DATABASE};
  1105. if ($repattrdevice eq $evl[1] && $repdb eq $own_hash->{DATABASE}) {
  1106. $attr{$_}{device} = $evl[2];
  1107. # Definitionsänderung wurde vorgenommen
  1108. $strucChanged = 1;
  1109. Log3 ($myName, 3, "DbRep Agent $myName - $_ attr device changed, old: \"$evl[1]\", new: \"$evl[2]\" ");
  1110. }
  1111. }
  1112. # if ($strucChanged) {CommandSave("","")};
  1113. }
  1114. }
  1115. return;
  1116. }
  1117. ###################################################################################
  1118. # DbRep_Undef
  1119. ###################################################################################
  1120. sub DbRep_Undef($$) {
  1121. my ($hash, $arg) = @_;
  1122. RemoveInternalTimer($hash);
  1123. my $dbh = $hash->{DBH};
  1124. $dbh->disconnect() if(defined($dbh));
  1125. BlockingKill($hash->{HELPER}{RUNNING_PID}) if (exists($hash->{HELPER}{RUNNING_PID}));
  1126. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BACKUP_CLIENT}));
  1127. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1128. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1129. DbRep_delread($hash,1);
  1130. return undef;
  1131. }
  1132. ###################################################################################
  1133. # DbRep_Shutdown
  1134. ###################################################################################
  1135. sub DbRep_Shutdown($) {
  1136. my ($hash) = @_;
  1137. my $dbh = $hash->{DBH};
  1138. $dbh->disconnect() if(defined($dbh));
  1139. DbRep_delread($hash,1);
  1140. RemoveInternalTimer($hash);
  1141. return undef;
  1142. }
  1143. ###################################################################################
  1144. # First Init DB Connect
  1145. # Verbindung zur DB aufbauen und den Timestamp des ältesten
  1146. # Datensatzes ermitteln
  1147. ###################################################################################
  1148. sub DbRep_firstconnect($) {
  1149. my ($hash) = @_;
  1150. my $name = $hash->{NAME};
  1151. my $to = "120";
  1152. my $dbloghash = $hash->{dbloghash};
  1153. my $dbconn = $dbloghash->{dbconn};
  1154. my $dbuser = $dbloghash->{dbuser};
  1155. RemoveInternalTimer($hash, "DbRep_firstconnect");
  1156. return if(IsDisabled($name));
  1157. if ($init_done == 1) {
  1158. Log3 ($name, 3, "DbRep $name - Connectiontest to database $dbconn with user $dbuser") if($hash->{LASTCMD} ne "minTimestamp");
  1159. $hash->{HELPER}{RUNNING_PID} = BlockingCall("DbRep_getMinTs", "$name", "DbRep_getMinTsDone", $to, "DbRep_getMinTsAborted", $hash);
  1160. $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057
  1161. } else {
  1162. InternalTimer(time+1, "DbRep_firstconnect", $hash, 0);
  1163. }
  1164. return;
  1165. }
  1166. ####################################################################################################
  1167. # den ältesten Datensatz (Timestamp) in der DB bestimmen
  1168. ####################################################################################################
  1169. sub DbRep_getMinTs($) {
  1170. my ($name) = @_;
  1171. my $hash = $defs{$name};
  1172. my $dbloghash = $hash->{dbloghash};
  1173. my $dbconn = $dbloghash->{dbconn};
  1174. my $dbuser = $dbloghash->{dbuser};
  1175. my $dblogname = $dbloghash->{NAME};
  1176. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1177. my $mintsdef = "1970-01-01 01:00:00";
  1178. my ($dbh,$sql,$err,$mints);
  1179. # Background-Startzeit
  1180. my $bst = [gettimeofday];
  1181. eval { $dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 }); };
  1182. if ($@) {
  1183. $err = encode_base64($@,"");
  1184. Log3 ($name, 2, "DbRep $name - $@");
  1185. return "$name|''|''|$err";
  1186. }
  1187. # SQL-Startzeit
  1188. my $st = [gettimeofday];
  1189. eval { $mints = $dbh->selectrow_array("SELECT min(TIMESTAMP) FROM history;"); };
  1190. $dbh->disconnect;
  1191. # SQL-Laufzeit ermitteln
  1192. my $rt = tv_interval($st);
  1193. $mints = $mints?encode_base64($mints,""):encode_base64($mintsdef,"");
  1194. # Background-Laufzeit ermitteln
  1195. my $brt = tv_interval($bst);
  1196. $rt = $rt.",".$brt;
  1197. return "$name|$mints|$rt|0";
  1198. }
  1199. ####################################################################################################
  1200. # Auswertungsroutine den ältesten Datensatz (Timestamp) in der DB bestimmen
  1201. ####################################################################################################
  1202. sub DbRep_getMinTsDone($) {
  1203. my ($string) = @_;
  1204. my @a = split("\\|",$string);
  1205. my $hash = $defs{$a[0]};
  1206. my $name = $hash->{NAME};
  1207. my $mints = decode_base64($a[1]);
  1208. my $bt = $a[2];
  1209. my ($rt,$brt) = split(",", $bt);
  1210. my $err = $a[3]?decode_base64($a[3]):undef;
  1211. my $dblogdevice = $hash->{HELPER}{DBLOGDEVICE};
  1212. $hash->{dbloghash} = $defs{$dblogdevice};
  1213. my $dbconn = $hash->{dbloghash}{dbconn};
  1214. if ($err) {
  1215. readingsBeginUpdate($hash);
  1216. ReadingsBulkUpdateValue ($hash, "errortext", $err);
  1217. ReadingsBulkUpdateValue ($hash, "state", "disconnected");
  1218. readingsEndUpdate($hash, 1);
  1219. delete($hash->{HELPER}{RUNNING_PID});
  1220. Log3 ($name, 2, "DbRep $name - DB connect failed. Make sure credentials of database $hash->{DATABASE} are valid and database is reachable.");
  1221. return;
  1222. }
  1223. $hash->{HELPER}{MINTS} = $mints;
  1224. my $state = ($hash->{LASTCMD} eq "minTimestamp")?"done":"connected";
  1225. readingsBeginUpdate($hash);
  1226. ReadingsBulkUpdateValue ($hash, "timestamp_oldest_dataset", $mints) if($hash->{LASTCMD} eq "minTimestamp");
  1227. ReadingsBulkUpdateTimeState($hash,$brt,$rt,$state);
  1228. readingsEndUpdate($hash, 1);
  1229. Log3 ($name, 4, "DbRep $name - Connectiontest to db $dbconn successful") if($hash->{LASTCMD} ne "minTimestamp");
  1230. $hash->{HELPER}{MINTS} = $mints;
  1231. delete($hash->{HELPER}{RUNNING_PID});
  1232. return;
  1233. }
  1234. ####################################################################################################
  1235. # Abbruchroutine den ältesten Datensatz (Timestamp) in der DB bestimmen
  1236. ####################################################################################################
  1237. sub DbRep_getMinTsAborted(@) {
  1238. my ($hash,$cause) = @_;
  1239. my $name = $hash->{NAME};
  1240. $cause = $cause?$cause:"Timeout: process terminated";
  1241. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause");
  1242. readingsBeginUpdate($hash);
  1243. ReadingsBulkUpdateValue ($hash, "errortext", $cause);
  1244. ReadingsBulkUpdateValue ($hash, "state", "disconnected");
  1245. readingsEndUpdate($hash, 1);
  1246. delete($hash->{HELPER}{RUNNING_PID});
  1247. return;
  1248. }
  1249. ################################################################################################################
  1250. # Hauptroutine
  1251. ################################################################################################################
  1252. sub DbRep_Main($$;$) {
  1253. my ($hash,$opt,$prop) = @_;
  1254. my $name = $hash->{NAME};
  1255. my $to = AttrVal($name, "timeout", "86400");
  1256. my $reading = AttrVal($name, "reading", "%");
  1257. my $device = AttrVal($name, "device", "%");
  1258. my $dbloghash = $hash->{dbloghash};
  1259. my $dbmodel = $dbloghash->{MODEL};
  1260. # Entkommentieren für Testroutine im Vordergrund
  1261. # testexit($hash);
  1262. return if( ($hash->{HELPER}{RUNNING_BACKUP_CLIENT} ||
  1263. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} ||
  1264. $hash->{HELPER}{RUNNING_REPAIR} ||
  1265. $hash->{HELPER}{RUNNING_OPTIMIZE}) &&
  1266. $opt !~ /dumpMySQL|restoreMySQL|dumpSQLite|restoreSQLite|optimizeTables|vacuum|repairSQLite/ );
  1267. # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  1268. DbRep_delread($hash);
  1269. if ($opt =~ /dumpMySQL|dumpSQLite/) {
  1270. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BACKUP_CLIENT}));
  1271. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1272. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1273. if($dbmodel =~ /MYSQL/) {
  1274. if ($prop eq "serverSide") {
  1275. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_DoDumpServerSide", "$name", "DbRep_DumpDone", $to, "DbRep_DumpAborted", $hash);
  1276. ReadingsSingleUpdateValue ($hash, "state", "serverSide Dump is running - be patient and see Logfile !", 1);
  1277. } else {
  1278. $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("mysql_DoDumpClientSide", "$name", "DbRep_DumpDone", $to, "DbRep_DumpAborted", $hash);
  1279. ReadingsSingleUpdateValue ($hash, "state", "clientSide Dump is running - be patient and see Logfile !", 1);
  1280. }
  1281. }
  1282. if($dbmodel =~ /SQLITE/) {
  1283. $hash->{HELPER}{RUNNING_BACKUP_CLIENT} = BlockingCall("DbRep_sqliteDoDump", "$name", "DbRep_DumpDone", $to, "DbRep_DumpAborted", $hash);
  1284. ReadingsSingleUpdateValue ($hash, "state", "SQLite Dump is running - be patient and see Logfile !", 1);
  1285. }
  1286. return;
  1287. }
  1288. if ($opt =~ /restoreMySQL/) {
  1289. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1290. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1291. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("mysql_RestoreServerSide", "$name|$prop", "DbRep_restoreDone", $to, "DbRep_restoreAborted", $hash);
  1292. ReadingsSingleUpdateValue ($hash, "state", "restore database is running - be patient and see Logfile !", 1);
  1293. return;
  1294. }
  1295. if ($opt =~ /restoreSQLite/) {
  1296. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1297. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1298. $hash->{HELPER}{RUNNING_BCKPREST_SERVER} = BlockingCall("DbRep_sqliteRestore", "$name|$prop", "DbRep_restoreDone", $to, "DbRep_restoreAborted", $hash);
  1299. ReadingsSingleUpdateValue ($hash, "state", "restore database is running - be patient and see Logfile !", 1);
  1300. return;
  1301. }
  1302. if ($opt =~ /optimizeTables|vacuum/) {
  1303. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1304. BlockingKill($hash->{HELPER}{RUNNING_BCKPREST_SERVER}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1305. $hash->{HELPER}{RUNNING_OPTIMIZE} = BlockingCall("DbRep_optimizeTables", "$name", "DbRep_OptimizeDone", $to, "DbRep_OptimizeAborted", $hash);
  1306. ReadingsSingleUpdateValue ($hash, "state", "optimize tables is running - be patient and see Logfile !", 1);
  1307. return;
  1308. }
  1309. if ($opt =~ /repairSQLite/) {
  1310. BlockingKill($hash->{HELPER}{RUNNING_BACKUP_CLIENT}) if (exists($hash->{HELPER}{RUNNING_BCKPREST_SERVER}));
  1311. BlockingKill($hash->{HELPER}{RUNNING_OPTIMIZE}) if (exists($hash->{HELPER}{RUNNING_OPTIMIZE}));
  1312. BlockingKill($hash->{HELPER}{RUNNING_REPAIR}) if (exists($hash->{HELPER}{RUNNING_REPAIR}));
  1313. $hash->{HELPER}{RUNNING_REPAIR} = BlockingCall("DbRep_sqliteRepair", "$name", "DbRep_RepairDone", $to, "DbRep_RepairAborted", $hash);
  1314. ReadingsSingleUpdateValue ($hash, "state", "repair database is running - be patient and see Logfile !", 1);
  1315. return;
  1316. }
  1317. if (exists($hash->{HELPER}{RUNNING_PID}) && $hash->{ROLE} ne "Agent") {
  1318. Log3 ($name, 3, "DbRep $name - WARNING - old process $hash->{HELPER}{RUNNING_PID}{pid} will be killed now to start a new BlockingCall");
  1319. BlockingKill($hash->{HELPER}{RUNNING_PID});
  1320. }
  1321. ReadingsSingleUpdateValue ($hash, "state", "running", 1);
  1322. # only for this block because of warnings if details of readings are not set
  1323. no warnings 'uninitialized';
  1324. # Ausgaben und Zeitmanipulationen
  1325. Log3 ($name, 4, "DbRep $name - -------- New selection --------- ");
  1326. Log3 ($name, 4, "DbRep $name - Command: $opt $prop");
  1327. # zentrales Timestamp-Array und Zeitgrenzen bereitstellen
  1328. my ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next);
  1329. my $ts = "no_aggregation"; # Dummy für eine Select-Schleife wenn != $IsTimeSet || $IsAggrSet
  1330. my ($IsTimeSet,$IsAggrSet,$aggregation) = DbRep_checktimeaggr($hash);
  1331. if($IsTimeSet || $IsAggrSet) {
  1332. ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts) = DbRep_createTimeArray($hash,$aggregation,$opt);
  1333. } else {
  1334. Log3 ($name, 4, "DbRep $name - Timestamp begin human readable: not set") if($opt !~ /tableCurrentPurge/);
  1335. Log3 ($name, 4, "DbRep $name - Timestamp end human readable: not set") if($opt !~ /tableCurrentPurge/);
  1336. }
  1337. Log3 ($name, 4, "DbRep $name - Aggregation: $aggregation") if($opt !~ /tableCurrentPurge|tableCurrentFillup|fetchrows|insert/);
  1338. ##### Funktionsaufrufe #####
  1339. if ($opt eq "sumValue") {
  1340. $hash->{HELPER}{RUNNING_PID} = BlockingCall("sumval_DoParse", "$name§$device§$reading§$prop§$ts", "sumval_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1341. } elsif ($opt =~ m/countEntries/) {
  1342. my $table = $prop;
  1343. $hash->{HELPER}{RUNNING_PID} = BlockingCall("count_DoParse", "$name§$table§$device§$reading§$ts", "count_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1344. } elsif ($opt eq "averageValue") {
  1345. Log3 ($name, 4, "DbRep $name - averageValue calculation sceme: ".AttrVal($name,"averageCalcForm","avgArithmeticMean"));
  1346. $hash->{HELPER}{RUNNING_PID} = BlockingCall("averval_DoParse", "$name§$device§$reading§$prop§$ts", "averval_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1347. } elsif ($opt eq "fetchrows") {
  1348. my $table = $prop;
  1349. $hash->{HELPER}{RUNNING_PID} = BlockingCall("fetchrows_DoParse", "$name|$table|$device|$reading|$runtime_string_first|$runtime_string_next", "fetchrows_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1350. } elsif ($opt =~ /delSeqDoublets/) {
  1351. my $cmd = $prop?$prop:"adviceRemain";
  1352. $hash->{HELPER}{RUNNING_PID} = BlockingCall("delseqdoubl_DoParse", "$name§$cmd§$device§$reading§$ts", "delseqdoubl_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1353. } elsif ($opt eq "exportToFile") {
  1354. $hash->{HELPER}{RUNNING_PID} = BlockingCall("expfile_DoParse", "$name§$device§$reading§$ts", "expfile_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1355. } elsif ($opt eq "importFromFile") {
  1356. $hash->{HELPER}{RUNNING_PID} = BlockingCall("impfile_Push", "$name", "impfile_PushDone", $to, "DbRep_ParseAborted", $hash);
  1357. } elsif ($opt eq "maxValue") {
  1358. $hash->{HELPER}{RUNNING_PID} = BlockingCall("maxval_DoParse", "$name§$device§$reading§$prop§$ts", "maxval_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1359. } elsif ($opt eq "minValue") {
  1360. $hash->{HELPER}{RUNNING_PID} = BlockingCall("minval_DoParse", "$name§$device§$reading§$prop§$ts", "minval_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1361. } elsif ($opt eq "delEntries") {
  1362. $hash->{HELPER}{RUNNING_PID} = BlockingCall("del_DoParse", "$name|history|$device|$reading|$runtime_string_first|$runtime_string_next", "del_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1363. } elsif ($opt eq "tableCurrentPurge") {
  1364. undef $runtime_string_first;
  1365. undef $runtime_string_next;
  1366. $hash->{HELPER}{RUNNING_PID} = BlockingCall("del_DoParse", "$name|current|$device|$reading|$runtime_string_first|$runtime_string_next", "del_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1367. } elsif ($opt eq "tableCurrentFillup") {
  1368. $hash->{HELPER}{RUNNING_PID} = BlockingCall("currentfillup_Push", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "currentfillup_Done", $to, "DbRep_ParseAborted", $hash);
  1369. } elsif ($opt eq "diffValue") {
  1370. $hash->{HELPER}{RUNNING_PID} = BlockingCall("diffval_DoParse", "$name§$device§$reading§$prop§$ts", "diffval_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1371. } elsif ($opt eq "insert") {
  1372. $hash->{HELPER}{RUNNING_PID} = BlockingCall("insert_Push", "$name", "insert_Done", $to, "DbRep_ParseAborted", $hash);
  1373. } elsif ($opt =~ /deviceRename|readingRename/) {
  1374. $hash->{HELPER}{RUNNING_PID} = BlockingCall("change_Push", "$name|$device|$reading|$runtime_string_first|$runtime_string_next", "change_Done", $to, "DbRep_ParseAborted", $hash);
  1375. } elsif ($opt =~ /changeValue/) {
  1376. $hash->{HELPER}{RUNNING_PID} = BlockingCall("changeval_Push", "$name§$device§$reading§$runtime_string_first§$runtime_string_next§$ts", "change_Done", $to, "DbRep_ParseAborted", $hash);
  1377. } elsif ($opt =~ /sqlCmd/ ) {
  1378. # Execute a generic sql command
  1379. $hash->{HELPER}{RUNNING_PID} = BlockingCall("sqlCmd_DoParse", "$name|$opt|$runtime_string_first|$runtime_string_next|$prop", "sqlCmd_ParseDone", $to, "DbRep_ParseAborted", $hash);
  1380. } elsif ($opt =~ /syncStandby/ ) {
  1381. # Befehl vor Procedure ausführen
  1382. DbRep_beforeproc($hash, "syncStandby");
  1383. $hash->{HELPER}{RUNNING_PID} = BlockingCall("DbRep_syncStandby", "$name§$device§$reading§$runtime_string_first§$runtime_string_next§$ts§$prop", "DbRep_syncStandbyDone", $to, "DbRep_ParseAborted", $hash);
  1384. }
  1385. $hash->{HELPER}{RUNNING_PID}{loglevel} = 5 if($hash->{HELPER}{RUNNING_PID}); # Forum #77057
  1386. return;
  1387. }
  1388. ################################################################################################################
  1389. # Create zentrales Timsstamp-Array
  1390. ################################################################################################################
  1391. sub DbRep_createTimeArray($$$) {
  1392. my ($hash,$aggregation,$opt) = @_;
  1393. my $name = $hash->{NAME};
  1394. # year als Jahre seit 1900
  1395. # $mon als 0..11
  1396. # $time = timelocal( $sec, $min, $hour, $mday, $mon, $year );
  1397. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # Istzeit Ableitung
  1398. my ($tsbegin,$tsend,$dim,$tsub,$tadd);
  1399. my ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear);
  1400. ######################################################################################
  1401. # absolute Auswertungszeiträume statische und dynamische (Beginn / Ende) berechnen
  1402. ######################################################################################
  1403. my $mints = $hash->{HELPER}{MINTS}?$hash->{HELPER}{MINTS}:"1970-01-01 01:00:00"; # Timestamp des 1. Datensatzes verwenden falls ermittelt
  1404. $tsbegin = AttrVal($name, "timestamp_begin", $mints);
  1405. $tsbegin = DbRep_formatpicker($tsbegin);
  1406. $tsend = AttrVal($name, "timestamp_end", strftime "%Y-%m-%d %H:%M:%S", localtime(time));
  1407. $tsend = DbRep_formatpicker($tsend);
  1408. if ( my $tap = AttrVal($name, "timeYearPeriod", undef)) {
  1409. # a b c d
  1410. # 06-01 02-28 , wenn c < a && $mon < a -> Jahr(a)-1, sonst Jahr(c)+1
  1411. my $ybp = $year+1900;
  1412. my $yep = $year+1900;
  1413. $tap =~ qr/^(\d{2})-(\d{2}) (\d{2})-(\d{2})$/p;
  1414. my $mbp = $1;
  1415. my $dbp = $2;
  1416. my $mep = $3;
  1417. my $dep = $4;
  1418. my $c = ($mon+1).$mday;
  1419. my $e = $mep.$dep;
  1420. if ($mep <= $mbp && $c <= $e) {
  1421. $ybp--;
  1422. } else {
  1423. $yep++;
  1424. }
  1425. $tsbegin = "$ybp-$mbp-$dbp 00:00:00";
  1426. $tsend = "$yep-$mep-$dep 23:59:59";
  1427. }
  1428. if (AttrVal($name,"timestamp_begin","") eq "current_year_begin" ||
  1429. AttrVal($name,"timestamp_end","") eq "current_year_begin") {
  1430. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year)) if(AttrVal($name,"timestamp_begin","") eq "current_year_begin");
  1431. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year)) if(AttrVal($name,"timestamp_end","") eq "current_year_begin");
  1432. }
  1433. if (AttrVal($name, "timestamp_begin", "") eq "current_year_end" ||
  1434. AttrVal($name, "timestamp_end", "") eq "current_year_end") {
  1435. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year)) if(AttrVal($name,"timestamp_begin","") eq "current_year_end");
  1436. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year)) if(AttrVal($name,"timestamp_end","") eq "current_year_end");
  1437. }
  1438. if (AttrVal($name, "timestamp_begin", "") eq "previous_year_begin" ||
  1439. AttrVal($name, "timestamp_end", "") eq "previous_year_begin") {
  1440. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year-1)) if(AttrVal($name, "timestamp_begin", "") eq "previous_year_begin");
  1441. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,0,$year-1)) if(AttrVal($name, "timestamp_end", "") eq "previous_year_begin");
  1442. }
  1443. if (AttrVal($name, "timestamp_begin", "") eq "previous_year_end" ||
  1444. AttrVal($name, "timestamp_end", "") eq "previous_year_end") {
  1445. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year-1)) if(AttrVal($name, "timestamp_begin", "") eq "previous_year_end");
  1446. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,31,11,$year-1)) if(AttrVal($name, "timestamp_end", "") eq "previous_year_end");
  1447. }
  1448. if (AttrVal($name, "timestamp_begin", "") eq "current_month_begin" ||
  1449. AttrVal($name, "timestamp_end", "") eq "current_month_begin") {
  1450. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_month_begin");
  1451. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_month_begin");
  1452. }
  1453. if (AttrVal($name, "timestamp_begin", "") eq "current_month_end" ||
  1454. AttrVal($name, "timestamp_end", "") eq "current_month_end") {
  1455. $dim = $mon-1?30+(($mon+1)*3%7<4):28+!($year%4||$year%400*!($year%100));
  1456. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_month_end");
  1457. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_month_end");
  1458. }
  1459. if (AttrVal($name, "timestamp_begin", "") eq "previous_month_begin" ||
  1460. AttrVal($name, "timestamp_end", "") eq "previous_month_begin") {
  1461. $ryear = ($mon-1<0)?$year-1:$year;
  1462. $rmon = ($mon-1<0)?11:$mon-1;
  1463. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_month_begin");
  1464. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,1,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_month_begin");
  1465. }
  1466. if (AttrVal($name, "timestamp_begin", "") eq "previous_month_end" ||
  1467. AttrVal($name, "timestamp_end", "") eq "previous_month_end") {
  1468. $ryear = ($mon-1<0)?$year-1:$year;
  1469. $rmon = ($mon-1<0)?11:$mon-1;
  1470. $dim = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100));
  1471. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_month_end");
  1472. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$dim,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_month_end");
  1473. }
  1474. if (AttrVal($name, "timestamp_begin", "") eq "current_week_begin" ||
  1475. AttrVal($name, "timestamp_end", "") eq "current_week_begin") {
  1476. $tsub = 0 if($wday == 1); # wenn Start am "Mo" keine Korrektur
  1477. $tsub = 86400 if($wday == 2); # wenn Start am "Di" dann Korrektur -1 Tage
  1478. $tsub = 172800 if($wday == 3); # wenn Start am "Mi" dann Korrektur -2 Tage
  1479. $tsub = 259200 if($wday == 4); # wenn Start am "Do" dann Korrektur -3 Tage
  1480. $tsub = 345600 if($wday == 5); # wenn Start am "Fr" dann Korrektur -4 Tage
  1481. $tsub = 432000 if($wday == 6); # wenn Start am "Sa" dann Korrektur -5 Tage
  1482. $tsub = 518400 if($wday == 0); # wenn Start am "So" dann Korrektur -6 Tage
  1483. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1484. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "current_week_begin");
  1485. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "current_week_begin");
  1486. }
  1487. if (AttrVal($name, "timestamp_begin", "") eq "current_week_end" ||
  1488. AttrVal($name, "timestamp_end", "") eq "current_week_end") {
  1489. $tadd = 518400 if($wday == 1); # wenn Start am "Mo" dann Korrektur +6 Tage
  1490. $tadd = 432000 if($wday == 2); # wenn Start am "Di" dann Korrektur +5 Tage
  1491. $tadd = 345600 if($wday == 3); # wenn Start am "Mi" dann Korrektur +4 Tage
  1492. $tadd = 259200 if($wday == 4); # wenn Start am "Do" dann Korrektur +3 Tage
  1493. $tadd = 172800 if($wday == 5); # wenn Start am "Fr" dann Korrektur +2 Tage
  1494. $tadd = 86400 if($wday == 6); # wenn Start am "Sa" dann Korrektur +1 Tage
  1495. $tadd = 0 if($wday == 0); # wenn Start am "So" keine Korrektur
  1496. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time+$tadd);
  1497. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "current_week_end");
  1498. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "current_week_end");
  1499. }
  1500. if (AttrVal($name, "timestamp_begin", "") eq "previous_week_begin" ||
  1501. AttrVal($name, "timestamp_end", "") eq "previous_week_begin") {
  1502. $tsub = 604800 if($wday == 1); # wenn Start am "Mo" dann Korrektur -7 Tage
  1503. $tsub = 691200 if($wday == 2); # wenn Start am "Di" dann Korrektur -8 Tage
  1504. $tsub = 777600 if($wday == 3); # wenn Start am "Mi" dann Korrektur -9 Tage
  1505. $tsub = 864000 if($wday == 4); # wenn Start am "Do" dann Korrektur -10 Tage
  1506. $tsub = 950400 if($wday == 5); # wenn Start am "Fr" dann Korrektur -11 Tage
  1507. $tsub = 1036800 if($wday == 6); # wenn Start am "Sa" dann Korrektur -12 Tage
  1508. $tsub = 1123200 if($wday == 0); # wenn Start am "So" dann Korrektur -13 Tage
  1509. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1510. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_week_begin");
  1511. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_week_begin");
  1512. }
  1513. if (AttrVal($name, "timestamp_begin", "") eq "previous_week_end" ||
  1514. AttrVal($name, "timestamp_end", "") eq "previous_week_end") {
  1515. $tsub = 86400 if($wday == 1); # wenn Start am "Mo" dann Korrektur -1 Tage
  1516. $tsub = 172800 if($wday == 2); # wenn Start am "Di" dann Korrektur -2 Tage
  1517. $tsub = 259200 if($wday == 3); # wenn Start am "Mi" dann Korrektur -3 Tage
  1518. $tsub = 345600 if($wday == 4); # wenn Start am "Do" dann Korrektur -4 Tage
  1519. $tsub = 432000 if($wday == 5); # wenn Start am "Fr" dann Korrektur -5 Tage
  1520. $tsub = 518400 if($wday == 6); # wenn Start am "Sa" dann Korrektur -6 Tage
  1521. $tsub = 604800 if($wday == 0); # wenn Start am "So" dann Korrektur -7 Tage
  1522. ($rsec,$rmin,$rhour,$rmday,$rmon,$ryear) = localtime(time-$tsub);
  1523. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_week_end");
  1524. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_week_end");
  1525. }
  1526. if (AttrVal($name, "timestamp_begin", "") eq "current_day_begin" ||
  1527. AttrVal($name, "timestamp_end", "") eq "current_day_begin") {
  1528. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$mday,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_day_begin");
  1529. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$mday,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_day_begin");
  1530. }
  1531. if (AttrVal($name, "timestamp_begin", "") eq "current_day_end" ||
  1532. AttrVal($name, "timestamp_end", "") eq "current_day_end") {
  1533. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$mday,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_day_end");
  1534. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$mday,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_day_end");
  1535. }
  1536. if (AttrVal($name, "timestamp_begin", "") eq "previous_day_begin" ||
  1537. AttrVal($name, "timestamp_end", "") eq "previous_day_begin") {
  1538. $rmday = $mday-1;
  1539. $rmon = $mon;
  1540. $ryear = $year;
  1541. if($rmday<1) {
  1542. $rmon--;
  1543. if ($rmon<0) {
  1544. $rmon=11;
  1545. $ryear--;
  1546. }
  1547. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1548. }
  1549. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_day_begin");
  1550. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,0,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_day_begin");
  1551. }
  1552. if (AttrVal($name, "timestamp_begin", "") eq "previous_day_end" ||
  1553. AttrVal($name, "timestamp_end", "") eq "previous_day_end") {
  1554. $rmday = $mday-1;
  1555. $rmon = $mon;
  1556. $ryear = $year;
  1557. if($rmday<1) {
  1558. $rmon--;
  1559. if ($rmon<0) {
  1560. $rmon=11;
  1561. $ryear--;
  1562. }
  1563. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1564. }
  1565. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_day_end");
  1566. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,23,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_day_end");
  1567. }
  1568. if (AttrVal($name, "timestamp_begin", "") eq "current_hour_begin" ||
  1569. AttrVal($name, "timestamp_end", "") eq "current_hour_begin") {
  1570. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$hour,$mday,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_hour_begin");
  1571. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$hour,$mday,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_hour_begin");
  1572. }
  1573. if (AttrVal($name, "timestamp_begin", "") eq "current_hour_end" ||
  1574. AttrVal($name, "timestamp_end", "") eq "current_hour_end") {
  1575. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$hour,$mday,$mon,$year)) if(AttrVal($name, "timestamp_begin", "") eq "current_hour_end");
  1576. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$hour,$mday,$mon,$year)) if(AttrVal($name, "timestamp_end", "") eq "current_hour_end");
  1577. }
  1578. if (AttrVal($name, "timestamp_begin", "") eq "previous_hour_begin" ||
  1579. AttrVal($name, "timestamp_end", "") eq "previous_hour_begin") {
  1580. $rhour = $hour-1;
  1581. $rmday = $mday;
  1582. $rmon = $mon;
  1583. $ryear = $year;
  1584. if($rhour<0) {
  1585. $rhour = 23;
  1586. $rmday = $mday-1;
  1587. if($rmday<1) {
  1588. $rmon--;
  1589. if ($rmon<0) {
  1590. $rmon=11;
  1591. $ryear--;
  1592. }
  1593. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1594. }
  1595. }
  1596. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_hour_begin");
  1597. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(0,0,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_hour_begin");
  1598. }
  1599. if (AttrVal($name, "timestamp_begin", "") eq "previous_hour_end" ||
  1600. AttrVal($name, "timestamp_end", "") eq "previous_hour_end") {
  1601. $rhour = $hour-1;
  1602. $rmday = $mday;
  1603. $rmon = $mon;
  1604. $ryear = $year;
  1605. if($rhour<0) {
  1606. $rhour = 23;
  1607. $rmday = $mday-1;
  1608. if($rmday<1) {
  1609. $rmon--;
  1610. if ($rmon<0) {
  1611. $rmon=11;
  1612. $ryear--;
  1613. }
  1614. $rmday = $rmon-1?30+(($rmon+1)*3%7<4):28+!($ryear%4||$ryear%400*!($ryear%100)); # Achtung: Monat als 1...12 (statt 0...11)
  1615. }
  1616. }
  1617. $tsbegin = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_begin", "") eq "previous_hour_end");
  1618. $tsend = strftime "%Y-%m-%d %T",localtime(timelocal(59,59,$rhour,$rmday,$rmon,$ryear)) if(AttrVal($name, "timestamp_end", "") eq "previous_hour_end");
  1619. }
  1620. # extrahieren der Einzelwerte von Datum/Zeit Beginn
  1621. my ($yyyy1, $mm1, $dd1, $hh1, $min1, $sec1) = ($tsbegin =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  1622. # extrahieren der Einzelwerte von Datum/Zeit Ende
  1623. my ($yyyy2, $mm2, $dd2, $hh2, $min2, $sec2) = ($tsend =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  1624. ######################################################################################
  1625. # relative Auswertungszeit Beginn berücksichtigen
  1626. ######################################################################################
  1627. # Umwandeln in Epochesekunden Beginn
  1628. my $epoch_seconds_begin = timelocal($sec1, $min1, $hh1, $dd1, $mm1-1, $yyyy1-1900) if($tsbegin);
  1629. my ($timeolderthan,$timedifftonow) = DbRep_normRelTime($hash);
  1630. if($timedifftonow) {
  1631. $epoch_seconds_begin = time() - $timedifftonow;
  1632. Log3 ($name, 4, "DbRep $name - Time difference to current time for calculating Timestamp begin: $timedifftonow sec");
  1633. } elsif ($timeolderthan) {
  1634. $epoch_seconds_begin = timelocal(00, 00, 01, 01, 01-1, 1970-1900);
  1635. }
  1636. my $tsbegin_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_begin);
  1637. Log3 ($name, 5, "DbRep $name - Timestamp begin epocheseconds: $epoch_seconds_begin") if($opt !~ /tableCurrentPurge/);
  1638. Log3 ($name, 4, "DbRep $name - Timestamp begin human readable: $tsbegin_string") if($opt !~ /tableCurrentPurge/);
  1639. ######################################################################################
  1640. # relative Auswertungszeit Ende berücksichtigen
  1641. ######################################################################################
  1642. # Umwandeln in Epochesekunden Endezeit
  1643. my $epoch_seconds_end = timelocal($sec2, $min2, $hh2, $dd2, $mm2-1, $yyyy2-1900);
  1644. $epoch_seconds_end = $timeolderthan ? (time() - $timeolderthan) : $epoch_seconds_end;
  1645. #$epoch_seconds_end = AttrVal($name, "timeOlderThan", undef) ?
  1646. # (time() - AttrVal($name, "timeOlderThan", undef)) : $epoch_seconds_end;
  1647. Log3 ($name, 4, "DbRep $name - Time difference to current time for calculating Timestamp end: $timeolderthan sec") if(AttrVal($name, "timeOlderThan", undef));
  1648. my $tsend_string = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1649. Log3 ($name, 5, "DbRep $name - Timestamp end epocheseconds: $epoch_seconds_end") if($opt !~ /tableCurrentPurge/);
  1650. Log3 ($name, 4, "DbRep $name - Timestamp end human readable: $tsend_string") if($opt !~ /tableCurrentPurge/);
  1651. ######################################################################################
  1652. # Erstellung Wertehash für Aggregationen
  1653. ######################################################################################
  1654. my $runtime = $epoch_seconds_begin; # Schleifenlaufzeit auf Beginn der Zeitselektion setzen
  1655. my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
  1656. my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
  1657. my $runtime_string_next; # Datum/Zeit + Periode (Granularität) für Auswertungsende im SQL-Format
  1658. my $reading_runtime_string; # zusammengesetzter Readingname+Aggregation für Update
  1659. my $tsstr = strftime "%H:%M:%S", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
  1660. my $testr = strftime "%H:%M:%S", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
  1661. my $dsstr = strftime "%Y-%m-%d", localtime($runtime); # für Berechnung Tagesverschieber / Stundenverschieber
  1662. my $destr = strftime "%Y-%m-%d", localtime($epoch_seconds_end); # für Berechnung Tagesverschieber / Stundenverschieber
  1663. my $msstr = strftime "%m", localtime($runtime); # Startmonat für Berechnung Monatsverschieber
  1664. my $mestr = strftime "%m", localtime($epoch_seconds_end); # Endemonat für Berechnung Monatsverschieber
  1665. my $ysstr = strftime "%Y", localtime($runtime); # Startjahr für Berechnung Monatsverschieber
  1666. my $yestr = strftime "%Y", localtime($epoch_seconds_end); # Endejahr für Berechnung Monatsverschieber
  1667. my $wd = strftime "%a", localtime($runtime); # Wochentag des aktuellen Startdatum/Zeit
  1668. my $wdadd = 604800 if($wd eq "Mo"); # wenn Start am "Mo" dann nächste Grenze +7 Tage
  1669. $wdadd = 518400 if($wd eq "Di"); # wenn Start am "Di" dann nächste Grenze +6 Tage
  1670. $wdadd = 432000 if($wd eq "Mi"); # wenn Start am "Mi" dann nächste Grenze +5 Tage
  1671. $wdadd = 345600 if($wd eq "Do"); # wenn Start am "Do" dann nächste Grenze +4 Tage
  1672. $wdadd = 259200 if($wd eq "Fr"); # wenn Start am "Fr" dann nächste Grenze +3 Tage
  1673. $wdadd = 172800 if($wd eq "Sa"); # wenn Start am "Sa" dann nächste Grenze +2 Tage
  1674. $wdadd = 86400 if($wd eq "So"); # wenn Start am "So" dann nächste Grenze +1 Tage
  1675. Log3 ($name, 5, "DbRep $name - weekday of start for selection: $wd -> wdadd: $wdadd") if($wdadd);
  1676. my $aggsec;
  1677. if ($aggregation eq "hour") {
  1678. $aggsec = 3600;
  1679. } elsif ($aggregation eq "day") {
  1680. $aggsec = 86400;
  1681. } elsif ($aggregation eq "week") {
  1682. $aggsec = 604800;
  1683. } elsif ($aggregation eq "month") {
  1684. $aggsec = 2678400; # Initialwert, wird in DbRep_collaggstr für jeden Monat berechnet
  1685. } elsif ($aggregation eq "no") {
  1686. $aggsec = 1;
  1687. } else {
  1688. return;
  1689. }
  1690. my %cv = (
  1691. tsstr => $tsstr,
  1692. testr => $testr,
  1693. dsstr => $dsstr,
  1694. destr => $destr,
  1695. msstr => $msstr,
  1696. mestr => $mestr,
  1697. ysstr => $ysstr,
  1698. yestr => $yestr,
  1699. aggsec => $aggsec,
  1700. aggregation => $aggregation,
  1701. epoch_seconds_end => $epoch_seconds_end,
  1702. wdadd => $wdadd
  1703. );
  1704. $hash->{HELPER}{CV} = \%cv;
  1705. my $ts; # für Erstellung Timestamp-Array zur nonblocking SQL-Abarbeitung
  1706. my $i = 1; # Schleifenzähler -> nur Indikator für ersten Durchlauf -> anderer $runtime_string_first
  1707. my $ll; # loopindikator, wenn 1 = loopausstieg
  1708. # Aufbau Timestampstring mit Zeitgrenzen entsprechend Aggregation
  1709. while (!$ll) {
  1710. # collect aggregation strings
  1711. ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll) = DbRep_collaggstr($hash,$runtime,$i,$runtime_string_next);
  1712. $ts .= $runtime_string."#".$runtime_string_first."#".$runtime_string_next."|";
  1713. $i++;
  1714. }
  1715. return ($epoch_seconds_begin,$epoch_seconds_end,$runtime_string_first,$runtime_string_next,$ts);
  1716. }
  1717. ####################################################################################################
  1718. # Zusammenstellung Aggregationszeiträume
  1719. ####################################################################################################
  1720. sub DbRep_collaggstr($$$$) {
  1721. my ($hash,$runtime,$i,$runtime_string_next) = @_;
  1722. my $name = $hash->{NAME};
  1723. my $runtime_string; # Datum/Zeit im SQL-Format für Readingname Teilstring
  1724. my $runtime_string_first; # Datum/Zeit Auswertungsbeginn im SQL-Format für SQL-Statement
  1725. my $ll; # loopindikator, wenn 1 = loopausstieg
  1726. my $runtime_orig; # orig. runtime als Grundlage für Addition mit $aggsec
  1727. my $tsstr = $hash->{HELPER}{CV}{tsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1728. my $testr = $hash->{HELPER}{CV}{testr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1729. my $dsstr = $hash->{HELPER}{CV}{dsstr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1730. my $destr = $hash->{HELPER}{CV}{destr}; # für Berechnung Tagesverschieber / Stundenverschieber
  1731. my $msstr = $hash->{HELPER}{CV}{msstr}; # Startmonat für Berechnung Monatsverschieber
  1732. my $mestr = $hash->{HELPER}{CV}{mestr}; # Endemonat für Berechnung Monatsverschieber
  1733. my $ysstr = $hash->{HELPER}{CV}{ysstr}; # Startjahr für Berechnung Monatsverschieber
  1734. my $yestr = $hash->{HELPER}{CV}{yestr}; # Endejahr für Berechnung Monatsverschieber
  1735. my $aggregation = $hash->{HELPER}{CV}{aggregation}; # Aggregation
  1736. my $aggsec = $hash->{HELPER}{CV}{aggsec}; # laufende Aggregationssekunden
  1737. my $epoch_seconds_end = $hash->{HELPER}{CV}{epoch_seconds_end};
  1738. my $wdadd = $hash->{HELPER}{CV}{wdadd}; # Ergänzungstage. Starttag + Ergänzungstage = der folgende Montag (für week-Aggregation)
  1739. # only for this block because of warnings if some values not set
  1740. no warnings 'uninitialized';
  1741. # keine Aggregation (all between timestamps)
  1742. if ($aggregation eq "no") {
  1743. $runtime_string = "no_aggregation"; # für Readingname
  1744. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
  1745. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1746. $ll = 1;
  1747. }
  1748. # Monatsaggregation
  1749. if ($aggregation eq "month") {
  1750. $runtime_orig = $runtime;
  1751. # Hilfsrechnungen
  1752. my $rm = strftime "%m", localtime($runtime); # Monat des aktuell laufenden Startdatums d. SQL-Select
  1753. my $ry = strftime "%Y", localtime($runtime); # Jahr des aktuell laufenden Startdatums d. SQL-Select
  1754. my $dim = $rm-2?30+($rm*3%7<4):28+!($ry%4||$ry%400*!($ry%100)); # Anzahl Tage des aktuell laufenden Monats
  1755. Log3 ($name, 5, "DbRep $name - act year: $ry, act month: $rm, days in month: $dim, endyear: $yestr, endmonth: $mestr");
  1756. $aggsec = $dim * 86400;
  1757. $runtime = $runtime+3600 if(DbRep_dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1758. $runtime_string = strftime "%Y-%m", localtime($runtime); # für Readingname
  1759. if ($i==1) {
  1760. # nur im ersten Durchlauf
  1761. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime_orig);
  1762. }
  1763. if ($ysstr == $yestr && $msstr == $mestr || $ry == $yestr && $rm == $mestr) {
  1764. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
  1765. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1766. $ll=1;
  1767. } else {
  1768. if(($runtime) > $epoch_seconds_end) {
  1769. #$runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>11); # ausgebaut 24.02.2018
  1770. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime);
  1771. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1772. $ll=1;
  1773. } else {
  1774. $runtime_string_first = strftime "%Y-%m-01", localtime($runtime) if($i>1);
  1775. $runtime_string_next = strftime "%Y-%m-01", localtime($runtime+($dim*86400));
  1776. }
  1777. }
  1778. my ($yyyy1, $mm1, $dd1) = ($runtime_string_next =~ /(\d+)-(\d+)-(\d+)/);
  1779. $runtime = timelocal("00", "00", "00", "01", $mm1-1, $yyyy1-1900);
  1780. # neue Beginnzeit in Epoche-Sekunden
  1781. $runtime = $runtime_orig+$aggsec;
  1782. }
  1783. # Wochenaggregation
  1784. if ($aggregation eq "week") {
  1785. $runtime = $runtime+3600 if($i!=1 && DbRep_dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1786. $runtime_orig = $runtime;
  1787. my $w = strftime "%V", localtime($runtime); # Wochennummer des aktuellen Startdatum/Zeit
  1788. $runtime_string = "week_".$w; # für Readingname
  1789. my $ms = strftime "%m", localtime($runtime); # Startmonat (01-12)
  1790. my $me = strftime "%m", localtime($epoch_seconds_end); # Endemonat (01-12)
  1791. if ($i==1) {
  1792. # nur im ersten Schleifendurchlauf
  1793. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime);
  1794. # Korrektur $runtime_orig für Berechnung neue Beginnzeit für nächsten Durchlauf
  1795. my ($yyyy1, $mm1, $dd1) = ($runtime_string_first =~ /(\d+)-(\d+)-(\d+)/);
  1796. $runtime = timelocal("00", "00", "00", $dd1, $mm1-1, $yyyy1-1900);
  1797. $runtime = $runtime+3600 if(DbRep_dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1798. $runtime = $runtime+$wdadd;
  1799. $runtime_orig = $runtime-$aggsec;
  1800. # die Woche Beginn ist gleich der Woche vom Ende Auswertung
  1801. if((strftime "%V", localtime($epoch_seconds_end)) eq ($w) && ($ms+$me != 13)) {
  1802. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1803. $ll=1;
  1804. } else {
  1805. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime);
  1806. }
  1807. } else {
  1808. # weitere Durchläufe
  1809. if(($runtime+$aggsec) > $epoch_seconds_end) {
  1810. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig);
  1811. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1812. $ll=1;
  1813. } else {
  1814. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime_orig) ;
  1815. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
  1816. }
  1817. }
  1818. # neue Beginnzeit in Epoche-Sekunden
  1819. $runtime = $runtime_orig+$aggsec;
  1820. }
  1821. # Tagesaggregation
  1822. if ($aggregation eq "day") {
  1823. $runtime_string = strftime "%Y-%m-%d", localtime($runtime); # für Readingname
  1824. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
  1825. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime) if($i>1);
  1826. $runtime = $runtime+3600 if(DbRep_dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1827. if((($tsstr gt $testr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
  1828. $runtime_string_first = strftime "%Y-%m-%d", localtime($runtime);
  1829. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr);
  1830. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1831. $ll=1;
  1832. } else {
  1833. $runtime_string_next = strftime "%Y-%m-%d", localtime($runtime+$aggsec);
  1834. }
  1835. Log3 ($name, 5, "DbRep $name - runtime_string: $runtime_string, runtime_string_first: $runtime_string_first, runtime_string_next: $runtime_string_next");
  1836. # neue Beginnzeit in Epoche-Sekunden
  1837. $runtime = $runtime+$aggsec;
  1838. }
  1839. # Stundenaggregation
  1840. if ($aggregation eq "hour") {
  1841. $runtime_string = strftime "%Y-%m-%d_%H", localtime($runtime); # für Readingname
  1842. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if($i==1);
  1843. $runtime = $runtime+3600 if(DbRep_dsttest($hash,$runtime,$aggsec) && (strftime "%m", localtime($runtime)) > 6); # Korrektur Winterzeitumstellung (Uhr wurde 1 Stunde zurück gestellt)
  1844. $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime) if($i>1);
  1845. my @a = split (":",$tsstr);
  1846. my $hs = $a[0];
  1847. my $msstr = $a[1].":".$a[2];
  1848. @a = split (":",$testr);
  1849. my $he = $a[0];
  1850. my $mestr = $a[1].":".$a[2];
  1851. if((($msstr gt $mestr) ? $runtime : ($runtime+$aggsec)) > $epoch_seconds_end) {
  1852. $runtime_string_first = strftime "%Y-%m-%d %H", localtime($runtime);
  1853. $runtime_string_first = strftime "%Y-%m-%d %H:%M:%S", localtime($runtime) if( $dsstr eq $destr && $hs eq $he);
  1854. $runtime_string_next = strftime "%Y-%m-%d %H:%M:%S", localtime($epoch_seconds_end);
  1855. $ll=1;
  1856. } else {
  1857. $runtime_string_next = strftime "%Y-%m-%d %H", localtime($runtime+$aggsec);
  1858. }
  1859. # neue Beginnzeit in Epoche-Sekunden
  1860. $runtime = $runtime+$aggsec;
  1861. }
  1862. return ($runtime,$runtime_string,$runtime_string_first,$runtime_string_next,$ll);
  1863. }
  1864. ####################################################################################################
  1865. # nichtblockierende DB-Abfrage averageValue
  1866. ####################################################################################################
  1867. sub averval_DoParse($) {
  1868. my ($string) = @_;
  1869. my ($name,$device,$reading,$prop,$ts) = split("\\§", $string);
  1870. my $hash = $defs{$name};
  1871. my $dbloghash = $hash->{dbloghash};
  1872. my $dbconn = $dbloghash->{dbconn};
  1873. my $dbuser = $dbloghash->{dbuser};
  1874. my $dblogname = $dbloghash->{NAME};
  1875. my $dbpassword = $attr{"sec$dblogname"}{secret};
  1876. my $acf = AttrVal($name, "averageCalcForm", "avgArithmeticMean"); # Festlegung Berechnungsschema f. Mittelwert
  1877. my $qlf = "avg";
  1878. my ($dbh,$sql,$sth,$err,$selspec,$addon);
  1879. # Background-Startzeit
  1880. my $bst = [gettimeofday];
  1881. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  1882. if ($@) {
  1883. $err = encode_base64($@,"");
  1884. Log3 ($name, 2, "DbRep $name - $@");
  1885. return "$name|''|$device|$reading|''|$err|''";
  1886. }
  1887. # only for this block because of warnings if details of readings are not set
  1888. no warnings 'uninitialized';
  1889. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  1890. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  1891. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  1892. # Timestampstring to Array
  1893. my @ts = split("\\|", $ts);
  1894. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  1895. if($acf eq "avgArithmeticMean") {
  1896. # arithmetischer Mittelwert
  1897. # vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  1898. $addon = '';
  1899. if ($dbloghash->{MODEL} eq "POSTGRESQL") {
  1900. $selspec = "AVG(VALUE::numeric)";
  1901. } elsif ($dbloghash->{MODEL} eq "MYSQL") {
  1902. $selspec = "AVG(VALUE)";
  1903. } elsif ($dbloghash->{MODEL} eq "SQLITE") {
  1904. $selspec = "AVG(VALUE)";
  1905. } else {
  1906. $selspec = "AVG(VALUE)";
  1907. }
  1908. $qlf = "avgam";
  1909. } elsif ($acf eq "avgDailyMeanGWS") {
  1910. # Tagesmittelwert Temperaturen nach Schema des deutschen Wetterdienstes
  1911. # SELECT VALUE FROM history WHERE DEVICE="MyWetter" AND READING="temperature" AND TIMESTAMP >= "2018-01-28 $i:00:00" AND TIMESTAMP <= "2018-01-28 ($i+1):00:00" ORDER BY TIMESTAMP DESC LIMIT 1;
  1912. $addon = "ORDER BY TIMESTAMP DESC LIMIT 1";
  1913. $selspec = "VALUE";
  1914. $qlf = "avgdmgws";
  1915. } elsif ($acf eq "avgTimeWeightMean") {
  1916. $addon = "ORDER BY TIMESTAMP ASC";
  1917. $selspec = "TIMESTAMP,VALUE";
  1918. $qlf = "avgtwm";
  1919. }
  1920. # SQL-Startzeit
  1921. my $st = [gettimeofday];
  1922. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  1923. my $arrstr;
  1924. foreach my $row (@ts) {
  1925. my @a = split("#", $row);
  1926. my $runtime_string = $a[0];
  1927. my $runtime_string_first = $a[1];
  1928. my $runtime_string_next = $a[2];
  1929. if($acf eq "avgArithmeticMean") {
  1930. # arithmetischer Mittelwert (Standard)
  1931. #
  1932. if ($IsTimeSet || $IsAggrSet) {
  1933. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon);
  1934. } else {
  1935. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,$addon);
  1936. }
  1937. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  1938. eval{ $sth = $dbh->prepare($sql);
  1939. $sth->execute();
  1940. };
  1941. if ($@) {
  1942. $err = encode_base64($@,"");
  1943. Log3 ($name, 2, "DbRep $name - $@");
  1944. $dbh->disconnect;
  1945. return "$name|''|$device|$reading|''|$err|''";
  1946. }
  1947. my @line = $sth->fetchrow_array();
  1948. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  1949. if(AttrVal($name, "aggregation", "") eq "hour") {
  1950. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  1951. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  1952. } else {
  1953. my @rsf = split(" ",$runtime_string_first);
  1954. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  1955. }
  1956. } elsif ($acf eq "avgDailyMeanGWS") {
  1957. # Berechnung des Tagesmittelwertes (Temperatur) nach der Vorschrift des deutschen Wetterdienstes
  1958. # Berechnung der Tagesmittel aus 24 Stundenwerten, Bezugszeit für einen Tag i.d.R. 23:51 UTC des
  1959. # Vortages bis 23:50 UTC, d.h. 00:51 bis 23:50 MEZ
  1960. # Wenn mehr als 3 Stundenwerte fehlen -> Berechnung aus den 4 Hauptterminen (00, 06, 12, 18 UTC),
  1961. # d.h. 01, 07, 13, 19 MEZ
  1962. # https://www.dwd.de/DE/leistungen/klimadatendeutschland/beschreibung_tagesmonatswerte.html
  1963. #
  1964. my $sum = 0;
  1965. my $anz = 0; # Anzahl der Messwerte am Tag
  1966. my($t01,$t07,$t13,$t19); # Temperaturen der Haupttermine
  1967. my ($bdate,undef) = split(" ",$runtime_string_first);
  1968. for my $i (0..23) {
  1969. my $bsel = $bdate." ".sprintf("%02d",$i).":00:00";
  1970. my $esel = ($i<23)?$bdate." ".sprintf("%02d",$i).":59:59":$runtime_string_next;
  1971. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$bsel'","'$esel'",$addon);
  1972. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  1973. eval{ $sth = $dbh->prepare($sql);
  1974. $sth->execute();
  1975. };
  1976. if ($@) {
  1977. $err = encode_base64($@,"");
  1978. Log3 ($name, 2, "DbRep $name - $@");
  1979. $dbh->disconnect;
  1980. return "$name|''|$device|$reading|''|$err|''";
  1981. }
  1982. my $val = $sth->fetchrow_array();
  1983. Log3 ($name, 5, "DbRep $name - SQL result: $val") if($val);
  1984. $val = DbRep_numval ($val); # nichtnumerische Zeichen eliminieren
  1985. if(defined($val) && looks_like_number($val)) {
  1986. $sum += $val;
  1987. $t01 = $val if($val && $i == 00); # Wert f. Stunde 01 ist zw. letzter Wert vor 01
  1988. $t07 = $val if($val && $i == 06);
  1989. $t13 = $val if($val && $i == 12);
  1990. $t19 = $val if($val && $i == 18);
  1991. $anz++;
  1992. }
  1993. }
  1994. if($anz >= 21) {
  1995. $sum = $sum/24;
  1996. } elsif ($anz >= 4 && $t01 && $t07 && $t13 && $t19) {
  1997. $sum = ($t01+$t07+$t13+$t19)/4;
  1998. } else {
  1999. $sum = "insufficient values";
  2000. }
  2001. if(AttrVal($name, "aggregation", "") eq "hour") {
  2002. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2003. $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."_".$rsf[1]."|";
  2004. } else {
  2005. my @rsf = split(" ",$runtime_string_first);
  2006. $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."|";
  2007. }
  2008. } elsif ($acf eq "avgTimeWeightMean") {
  2009. # zeitgewichteten Mittelwert berechnen
  2010. # http://massmatics.de/merkzettel/#!837:Gewichteter_Mittelwert
  2011. #
  2012. # $tsum = timestamp letzter Messpunkt - timestamp erster Messpunkt
  2013. # $t1 = timestamp wert1
  2014. # $t2 = timestamp wert2
  2015. # $dt = $t2 - $t1
  2016. # $t1 = $t2
  2017. # .....
  2018. # (val1*$dt/$tsum) + (val2*$dt/$tsum) + .... + (valn*$dt/$tsum)
  2019. #
  2020. # gesamte Zeitspanne $tsum zwischen ersten und letzten Datensatz der Zeitscheibe ermitteln
  2021. my ($tsum,$tf,$tl,$tn,$to,$dt,$val,$val1);
  2022. my $sum = 0;
  2023. my $addonf = 'ORDER BY TIMESTAMP ASC LIMIT 1';
  2024. my $addonl = 'ORDER BY TIMESTAMP DESC LIMIT 1';
  2025. my $sqlf = DbRep_createSelectSql($hash,"history","TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addonf);
  2026. my $sqll = DbRep_createSelectSql($hash,"history","TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addonl);
  2027. eval { $tf = ($dbh->selectrow_array($sqlf))[0];
  2028. $tl = ($dbh->selectrow_array($sqll))[0];
  2029. };
  2030. if ($@) {
  2031. $err = encode_base64($@,"");
  2032. Log3 ($name, 2, "DbRep $name - $@");
  2033. $dbh->disconnect;
  2034. return "$name|''|$device|$reading|''|$err|''";
  2035. }
  2036. if(!$tf || !$tl) {
  2037. # kein Start- und/oder Ende Timestamp in Zeitscheibe vorhanden -> keine Werteberechnung möglich
  2038. $sum = "insufficient values";
  2039. } else {
  2040. my ($yyyyf, $mmf, $ddf, $hhf, $minf, $secf) = ($tf =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  2041. my ($yyyyl, $mml, $ddl, $hhl, $minl, $secl) = ($tl =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  2042. $tsum = (timelocal($secl, $minl, $hhl, $ddl, $mml-1, $yyyyl-1900))-(timelocal($secf, $minf, $hhf, $ddf, $mmf-1, $yyyyf-1900));
  2043. if ($IsTimeSet || $IsAggrSet) {
  2044. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon);
  2045. } else {
  2046. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,$addon);
  2047. }
  2048. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2049. eval{ $sth = $dbh->prepare($sql);
  2050. $sth->execute();
  2051. };
  2052. if ($@) {
  2053. $err = encode_base64($@,"");
  2054. Log3 ($name, 2, "DbRep $name - $@");
  2055. $dbh->disconnect;
  2056. return "$name|''|$device|$reading|''|$err|''";
  2057. }
  2058. my @twm_array = map { $_->[0]."_ESC_".$_->[1] } @{$sth->fetchall_arrayref()};
  2059. foreach my $twmrow (@twm_array) {
  2060. ($tn,$val) = split("_ESC_",$twmrow);
  2061. $val = DbRep_numval ($val); # nichtnumerische Zeichen eliminieren
  2062. my ($yyyyt1, $mmt1, $ddt1, $hht1, $mint1, $sect1) = ($tn =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/);
  2063. $tn = timelocal($sect1, $mint1, $hht1, $ddt1, $mmt1-1, $yyyyt1-1900);
  2064. if(!$to) {
  2065. $val1 = $val;
  2066. $to = $tn;
  2067. next;
  2068. }
  2069. $dt = $tn - $to;
  2070. $sum += $val1*($dt/$tsum);
  2071. $val1 = $val;
  2072. $to = $tn;
  2073. Log3 ($name, 5, "DbRep $name - data element: $twmrow");
  2074. Log3 ($name, 5, "DbRep $name - time sum: $tsum, delta time: $dt, value: $val1, twm: ".$val1*($dt/$tsum));
  2075. }
  2076. }
  2077. if(AttrVal($name, "aggregation", "") eq "hour") {
  2078. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2079. $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."_".$rsf[1]."|";
  2080. } else {
  2081. my @rsf = split(" ",$runtime_string_first);
  2082. $arrstr .= $runtime_string."#".$sum."#".$rsf[0]."|";
  2083. }
  2084. }
  2085. }
  2086. $sth->finish;
  2087. $dbh->disconnect;
  2088. # SQL-Laufzeit ermitteln
  2089. my $rt = tv_interval($st);
  2090. # Ergebnisse in Datenbank schreiben
  2091. my ($wrt,$irowdone);
  2092. if($prop =~ /writeToDB/) {
  2093. ($wrt,$irowdone,$err) = DbRep_OutputWriteToDB($name,$device,$reading,$arrstr,$qlf);
  2094. if ($err) {
  2095. Log3 $hash->{NAME}, 2, "DbRep $name - $err";
  2096. $err = encode_base64($err,"");
  2097. return "$name|''|$device|$reading|''|$err|''";
  2098. }
  2099. $rt = $rt+$wrt;
  2100. }
  2101. # Daten müssen als Einzeiler zurückgegeben werden
  2102. $arrstr = encode_base64($arrstr,"");
  2103. # Background-Laufzeit ermitteln
  2104. my $brt = tv_interval($bst);
  2105. $rt = $rt.",".$brt;
  2106. return "$name|$arrstr|$device|$reading|$rt|0|$irowdone";
  2107. }
  2108. ####################################################################################################
  2109. # Auswertungsroutine der nichtblockierenden DB-Abfrage averageValue
  2110. ####################################################################################################
  2111. sub averval_ParseDone($) {
  2112. my ($string) = @_;
  2113. my @a = split("\\|",$string);
  2114. my $hash = $defs{$a[0]};
  2115. my $name = $hash->{NAME};
  2116. my $arrstr = decode_base64($a[1]);
  2117. my $device = $a[2];
  2118. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2119. my $reading = $a[3];
  2120. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2121. my $bt = $a[4];
  2122. my ($rt,$brt) = split(",", $bt);
  2123. my $err = $a[5]?decode_base64($a[5]):undef;
  2124. my $irowdone = $a[6];
  2125. my $reading_runtime_string;
  2126. if ($err) {
  2127. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2128. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2129. delete($hash->{HELPER}{RUNNING_PID});
  2130. return;
  2131. }
  2132. # only for this block because of warnings if details of readings are not set
  2133. no warnings 'uninitialized';
  2134. my $acf = AttrVal($name, "averageCalcForm", "avgArithmeticMean");
  2135. if($acf eq "avgArithmeticMean") {
  2136. $acf = "AM"
  2137. } elsif ($acf eq "avgDailyMeanGWS") {
  2138. $acf = "DMGWS";
  2139. } elsif ($acf eq "avgTimeWeightMean") {
  2140. $acf = "TWM";
  2141. }
  2142. # Readingaufbereitung
  2143. readingsBeginUpdate($hash);
  2144. my @arr = split("\\|", $arrstr);
  2145. foreach my $row (@arr) {
  2146. my @a = split("#", $row);
  2147. my $runtime_string = $a[0];
  2148. my $c = $a[1];
  2149. my $rsf = $a[2]."__";
  2150. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2151. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  2152. } else {
  2153. my $ds = $device."__" if ($device);
  2154. my $rds = $reading."__" if ($reading);
  2155. $reading_runtime_string = $rsf.$ds.$rds."AVG".$acf."__".$runtime_string;
  2156. }
  2157. if($acf eq "DMGWS") {
  2158. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, looks_like_number($c)?sprintf("%.1f",$c):$c);
  2159. } else {
  2160. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-");
  2161. }
  2162. }
  2163. ReadingsBulkUpdateValue ($hash, "db_lines_processed", $irowdone) if($hash->{LASTCMD} =~ /writeToDB/);
  2164. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2165. readingsEndUpdate($hash, 1);
  2166. delete($hash->{HELPER}{RUNNING_PID});
  2167. return;
  2168. }
  2169. ####################################################################################################
  2170. # nichtblockierende DB-Abfrage count
  2171. ####################################################################################################
  2172. sub count_DoParse($) {
  2173. my ($string) = @_;
  2174. my ($name,$table,$device,$reading,$ts) = split("\\§", $string);
  2175. my $hash = $defs{$name};
  2176. my $dbloghash = $hash->{dbloghash};
  2177. my $dbconn = $dbloghash->{dbconn};
  2178. my $dbuser = $dbloghash->{dbuser};
  2179. my $dblogname = $dbloghash->{NAME};
  2180. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2181. my ($dbh,$sql,$sth,$err,$selspec);
  2182. # Background-Startzeit
  2183. my $bst = [gettimeofday];
  2184. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2185. if ($@) {
  2186. $err = encode_base64($@,"");
  2187. Log3 ($name, 2, "DbRep $name - $@");
  2188. return "$name|''|$device|$reading|''|$err|$table";
  2189. }
  2190. # only for this block because of warnings if details of readings are not set
  2191. no warnings 'uninitialized';
  2192. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2193. my ($IsTimeSet,$IsAggrSet,$aggregation) = DbRep_checktimeaggr($hash);
  2194. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2195. # Timestampstring to Array
  2196. my @ts = split("\\|", $ts);
  2197. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2198. # SQL-Startzeit
  2199. my $st = [gettimeofday];
  2200. # DB-Abfrage zeilenweise für jeden Timearray-Eintrag
  2201. my $arrstr;
  2202. foreach my $row (@ts) {
  2203. my @a = split("#", $row);
  2204. my $runtime_string = $a[0];
  2205. my $runtime_string_first = $a[1];
  2206. my $runtime_string_next = $a[2];
  2207. if ($IsTimeSet || $IsAggrSet) {
  2208. $sql = DbRep_createSelectSql($hash,$table,"COUNT(*)",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  2209. } else {
  2210. $sql = DbRep_createSelectSql($hash,$table,"COUNT(*)",$device,$reading,undef,undef,'');
  2211. }
  2212. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2213. eval{ $sth = $dbh->prepare($sql);
  2214. $sth->execute();
  2215. };
  2216. if ($@) {
  2217. $err = encode_base64($@,"");
  2218. Log3 ($name, 2, "DbRep $name - $@");
  2219. $dbh->disconnect;
  2220. return "$name|''|$device|$reading|''|$err|$table";
  2221. }
  2222. # DB-Abfrage -> Ergebnis in @arr aufnehmen
  2223. my @line = $sth->fetchrow_array();
  2224. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  2225. if($aggregation eq "hour") {
  2226. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2227. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  2228. } else {
  2229. my @rsf = split(" ",$runtime_string_first);
  2230. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  2231. }
  2232. }
  2233. $sth->finish;
  2234. $dbh->disconnect;
  2235. # SQL-Laufzeit ermitteln
  2236. my $rt = tv_interval($st);
  2237. # Daten müssen als Einzeiler zurückgegeben werden
  2238. $arrstr = encode_base64($arrstr,"");
  2239. # Background-Laufzeit ermitteln
  2240. my $brt = tv_interval($bst);
  2241. $rt = $rt.",".$brt;
  2242. return "$name|$arrstr|$device|$reading|$rt|0|$table";
  2243. }
  2244. ####################################################################################################
  2245. # Auswertungsroutine der nichtblockierenden DB-Abfrage count
  2246. ####################################################################################################
  2247. sub count_ParseDone($) {
  2248. my ($string) = @_;
  2249. my @a = split("\\|",$string);
  2250. my $hash = $defs{$a[0]};
  2251. my $name = $hash->{NAME};
  2252. my $arrstr = decode_base64($a[1]);
  2253. my $device = $a[2];
  2254. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2255. my $reading = $a[3];
  2256. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2257. my $bt = $a[4];
  2258. my ($rt,$brt) = split(",", $bt);
  2259. my $err = $a[5]?decode_base64($a[5]):undef;
  2260. my $table = $a[6];
  2261. my $reading_runtime_string;
  2262. if ($err) {
  2263. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2264. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2265. delete($hash->{HELPER}{RUNNING_PID});
  2266. return;
  2267. }
  2268. Log3 ($name, 5, "DbRep $name - SQL result decoded: $arrstr") if($arrstr);
  2269. # only for this block because of warnings if details of readings are not set
  2270. no warnings 'uninitialized';
  2271. # Readingaufbereitung
  2272. readingsBeginUpdate($hash);
  2273. my @arr = split("\\|", $arrstr);
  2274. foreach my $row (@arr) {
  2275. my @a = split("#", $row);
  2276. my $runtime_string = $a[0];
  2277. my $c = $a[1];
  2278. my $rsf = $a[2]."__";
  2279. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2280. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  2281. } else {
  2282. my $ds = $device."__" if ($device);
  2283. my $rds = $reading."__" if ($reading);
  2284. $reading_runtime_string = $rsf.$ds.$rds."COUNT_".$table."__".$runtime_string;
  2285. }
  2286. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?$c:"-");
  2287. }
  2288. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2289. readingsEndUpdate($hash, 1);
  2290. delete($hash->{HELPER}{RUNNING_PID});
  2291. return;
  2292. }
  2293. ####################################################################################################
  2294. # nichtblockierende DB-Abfrage maxValue
  2295. ####################################################################################################
  2296. sub maxval_DoParse($) {
  2297. my ($string) = @_;
  2298. my ($name,$device,$reading,$prop,$ts) = split("\\§", $string);
  2299. my $hash = $defs{$name};
  2300. my $dbloghash = $hash->{dbloghash};
  2301. my $dbconn = $dbloghash->{dbconn};
  2302. my $dbuser = $dbloghash->{dbuser};
  2303. my $dblogname = $dbloghash->{NAME};
  2304. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2305. my ($dbh,$sql,$sth,$err);
  2306. # Background-Startzeit
  2307. my $bst = [gettimeofday];
  2308. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2309. if ($@) {
  2310. $err = encode_base64($@,"");
  2311. Log3 ($name, 2, "DbRep $name - $@");
  2312. return "$name|''|$device|$reading|''|$err|''";
  2313. }
  2314. # only for this block because of warnings if details of readings are not set
  2315. no warnings 'uninitialized';
  2316. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2317. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  2318. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2319. # Timestampstring to Array
  2320. my @ts = split("\\|", $ts);
  2321. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2322. # SQL-Startzeit
  2323. my $st = [gettimeofday];
  2324. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  2325. my @row_array;
  2326. foreach my $row (@ts) {
  2327. my @a = split("#", $row);
  2328. my $runtime_string = $a[0];
  2329. my $runtime_string_first = $a[1];
  2330. my $runtime_string_next = $a[2];
  2331. $runtime_string = encode_base64($runtime_string,"");
  2332. if ($IsTimeSet || $IsAggrSet) {
  2333. $sql = DbRep_createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP");
  2334. } else {
  2335. $sql = DbRep_createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,undef,undef,"ORDER BY TIMESTAMP");
  2336. }
  2337. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2338. eval{ $sth = $dbh->prepare($sql);
  2339. $sth->execute();
  2340. };
  2341. if ($@) {
  2342. $err = encode_base64($@,"");
  2343. Log3 ($name, 2, "DbRep $name - $@");
  2344. $dbh->disconnect;
  2345. return "$name|''|$device|$reading|''|$err|''";
  2346. }
  2347. my @array= map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  2348. if(!@array) {
  2349. if(AttrVal($name, "aggregation", "") eq "hour") {
  2350. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2351. @array = ($runtime_string." "."0"." ".$rsf[0]."_".$rsf[1]."\n");
  2352. } else {
  2353. my @rsf = split(" ",$runtime_string_first);
  2354. @array = ($runtime_string." "."0"." ".$rsf[0]."\n");
  2355. }
  2356. }
  2357. push(@row_array, @array);
  2358. }
  2359. $sth->finish;
  2360. $dbh->disconnect;
  2361. # SQL-Laufzeit ermitteln
  2362. my $rt = tv_interval($st);
  2363. Log3 ($name, 5, "DbRep $name -> raw data of row_array result:\n @row_array");
  2364. #---------- Berechnung Ergebnishash maxValue ------------------------
  2365. my $i = 1;
  2366. my %rh = ();
  2367. my ($lastruntimestring,$row_max_time,$max_value);
  2368. foreach my $row (@row_array) {
  2369. my @a = split("[ \t][ \t]*", $row);
  2370. my $runtime_string = decode_base64($a[0]);
  2371. $lastruntimestring = $runtime_string if ($i == 1);
  2372. my $value = $a[1];
  2373. $a[-1] =~ s/:/-/g if($a[-1]); # substituieren unsupported characters -> siehe fhem.pl
  2374. my $timestamp = ($a[-1]&&$a[-2])?$a[-2]."_".$a[-1]:$a[-1];
  2375. # Leerzeichen am Ende $timestamp entfernen
  2376. $timestamp =~ s/\s+$//g;
  2377. # Test auf $value = "numeric"
  2378. if (!looks_like_number($value)) {
  2379. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in maxValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  2380. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  2381. return "$name|''|$device|$reading|''|$err|''";
  2382. }
  2383. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, TIMESTAMP: $timestamp, VALUE: $value");
  2384. if ($runtime_string eq $lastruntimestring) {
  2385. if (!defined($max_value) || $value >= $max_value) {
  2386. $max_value = $value;
  2387. $row_max_time = $timestamp;
  2388. $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
  2389. }
  2390. } else {
  2391. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen
  2392. $lastruntimestring = $runtime_string;
  2393. undef $max_value;
  2394. if (!defined($max_value) || $value >= $max_value) {
  2395. $max_value = $value;
  2396. $row_max_time = $timestamp;
  2397. $rh{$runtime_string} = $runtime_string."|".$max_value."|".$row_max_time;
  2398. }
  2399. }
  2400. $i++;
  2401. }
  2402. #---------------------------------------------------------------------------------------------
  2403. Log3 ($name, 5, "DbRep $name - result of maxValue calculation before encoding:");
  2404. foreach my $key (sort(keys(%rh))) {
  2405. Log3 ($name, 5, "runtimestring Key: $key, value: ".$rh{$key});
  2406. }
  2407. # Ergebnishash als Einzeiler zurückgeben bzw. Übergabe Schreibroutine
  2408. my $rows = join('§', %rh);
  2409. # Ergebnisse in Datenbank schreiben
  2410. my ($wrt,$irowdone);
  2411. if($prop =~ /writeToDB/) {
  2412. ($wrt,$irowdone,$err) = DbRep_OutputWriteToDB($name,$device,$reading,$rows,"max");
  2413. if ($err) {
  2414. Log3 $hash->{NAME}, 2, "DbRep $name - $err";
  2415. $err = encode_base64($err,"");
  2416. return "$name|''|$device|$reading|''|$err|''";
  2417. }
  2418. $rt = $rt+$wrt;
  2419. }
  2420. my $rowlist = encode_base64($rows,"");
  2421. # Background-Laufzeit ermitteln
  2422. my $brt = tv_interval($bst);
  2423. $rt = $rt.",".$brt;
  2424. return "$name|$rowlist|$device|$reading|$rt|0|$irowdone";
  2425. }
  2426. ####################################################################################################
  2427. # Auswertungsroutine der nichtblockierenden DB-Abfrage maxValue
  2428. ####################################################################################################
  2429. sub maxval_ParseDone($) {
  2430. my ($string) = @_;
  2431. my @a = split("\\|",$string);
  2432. my $hash = $defs{$a[0]};
  2433. my $name = $hash->{NAME};
  2434. my $rowlist = decode_base64($a[1]);
  2435. my $device = $a[2];
  2436. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2437. my $reading = $a[3];
  2438. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2439. my $bt = $a[4];
  2440. my ($rt,$brt) = split(",", $bt);
  2441. my $err = $a[5]?decode_base64($a[5]):undef;
  2442. my $irowdone = $a[6];
  2443. my $reading_runtime_string;
  2444. if ($err) {
  2445. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2446. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2447. delete($hash->{HELPER}{RUNNING_PID});
  2448. return;
  2449. }
  2450. my %rh = split("§", $rowlist);
  2451. Log3 ($name, 5, "DbRep $name - result of maxValue calculation after decoding:");
  2452. foreach my $key (sort(keys(%rh))) {
  2453. Log3 ($name, 5, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  2454. }
  2455. # Readingaufbereitung
  2456. readingsBeginUpdate($hash);
  2457. # only for this block because of warnings if details of readings are not set
  2458. no warnings 'uninitialized';
  2459. foreach my $key (sort(keys(%rh))) {
  2460. my @k = split("\\|",$rh{$key});
  2461. my $rsf = $k[2]."__" if($k[2]);
  2462. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2463. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  2464. } else {
  2465. my $ds = $device."__" if ($device);
  2466. my $rds = $reading."__" if ($reading);
  2467. $reading_runtime_string = $rsf.$ds.$rds."MAX__".$k[0];
  2468. }
  2469. my $rv = $k[1];
  2470. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, defined($rv)?sprintf("%.4f",$rv):"-");
  2471. }
  2472. ReadingsBulkUpdateValue ($hash, "db_lines_processed", $irowdone) if($hash->{LASTCMD} =~ /writeToDB/);
  2473. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2474. readingsEndUpdate($hash, 1);
  2475. delete($hash->{HELPER}{RUNNING_PID});
  2476. return;
  2477. }
  2478. ####################################################################################################
  2479. # nichtblockierende DB-Abfrage minValue
  2480. ####################################################################################################
  2481. sub minval_DoParse($) {
  2482. my ($string) = @_;
  2483. my ($name,$device,$reading,$prop,$ts) = split("\\§", $string);
  2484. my $hash = $defs{$name};
  2485. my $dbloghash = $hash->{dbloghash};
  2486. my $dbconn = $dbloghash->{dbconn};
  2487. my $dbuser = $dbloghash->{dbuser};
  2488. my $dblogname = $dbloghash->{NAME};
  2489. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2490. my ($dbh,$sql,$sth,$err);
  2491. # Background-Startzeit
  2492. my $bst = [gettimeofday];
  2493. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2494. if ($@) {
  2495. $err = encode_base64($@,"");
  2496. Log3 ($name, 2, "DbRep $name - $@");
  2497. return "$name|''|$device|$reading|''|$err|''";
  2498. }
  2499. # only for this block because of warnings if details of readings are not set
  2500. no warnings 'uninitialized';
  2501. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2502. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  2503. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2504. # Timestampstring to Array
  2505. my @ts = split("\\|", $ts);
  2506. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2507. # SQL-Startzeit
  2508. my $st = [gettimeofday];
  2509. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  2510. my @row_array;
  2511. foreach my $row (@ts) {
  2512. my @a = split("#", $row);
  2513. my $runtime_string = $a[0];
  2514. my $runtime_string_first = $a[1];
  2515. my $runtime_string_next = $a[2];
  2516. $runtime_string = encode_base64($runtime_string,"");
  2517. if ($IsTimeSet || $IsAggrSet) {
  2518. $sql = DbRep_createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP");
  2519. } else {
  2520. $sql = DbRep_createSelectSql($hash,"history","VALUE,TIMESTAMP",$device,$reading,undef,undef,"ORDER BY TIMESTAMP");
  2521. }
  2522. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2523. eval{ $sth = $dbh->prepare($sql);
  2524. $sth->execute();
  2525. };
  2526. if ($@) {
  2527. $err = encode_base64($@,"");
  2528. Log3 ($name, 2, "DbRep $name - $@");
  2529. $dbh->disconnect;
  2530. return "$name|''|$device|$reading|''|$err|''";
  2531. }
  2532. my @array= map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  2533. if(!@array) {
  2534. if(AttrVal($name, "aggregation", "") eq "hour") {
  2535. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2536. @array = ($runtime_string." "."0"." ".$rsf[0]."_".$rsf[1]."\n");
  2537. } else {
  2538. my @rsf = split(" ",$runtime_string_first);
  2539. @array = ($runtime_string." "."0"." ".$rsf[0]."\n");
  2540. }
  2541. }
  2542. push(@row_array, @array);
  2543. }
  2544. $sth->finish;
  2545. $dbh->disconnect;
  2546. # SQL-Laufzeit ermitteln
  2547. my $rt = tv_interval($st);
  2548. Log3 ($name, 5, "DbRep $name -> raw data of row_array result:\n @row_array");
  2549. #---------- Berechnung Ergebnishash minValue ------------------------
  2550. my $i = 1;
  2551. my %rh = ();
  2552. my $lastruntimestring;
  2553. my $row_min_time;
  2554. my ($min_value,$value);
  2555. foreach my $row (@row_array) {
  2556. my @a = split("[ \t][ \t]*", $row);
  2557. my $runtime_string = decode_base64($a[0]);
  2558. $lastruntimestring = $runtime_string if ($i == 1);
  2559. $value = $a[1];
  2560. $min_value = $a[1] if ($i == 1);
  2561. $a[-1] =~ s/:/-/g if($a[-1]); # substituieren unsupported characters -> siehe fhem.pl
  2562. my $timestamp = ($a[-1]&&$a[-2])?$a[-2]."_".$a[-1]:$a[-1];
  2563. # Leerzeichen am Ende $timestamp entfernen
  2564. $timestamp =~ s/\s+$//g;
  2565. # Test auf $value = "numeric"
  2566. if (!looks_like_number($value)) {
  2567. # $a[-1] =~ s/\s+$//g;
  2568. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in minValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  2569. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  2570. return "$name|''|$device|$reading|''|$err|''";
  2571. }
  2572. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, TIMESTAMP: $timestamp, VALUE: $value");
  2573. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$timestamp if ($i == 1); # minValue des ersten SQL-Statements in hash einfügen
  2574. if ($runtime_string eq $lastruntimestring) {
  2575. if (!defined($min_value) || $value < $min_value) {
  2576. $min_value = $value;
  2577. $row_min_time = $timestamp;
  2578. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$row_min_time;
  2579. }
  2580. } else {
  2581. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen
  2582. $lastruntimestring = $runtime_string;
  2583. $min_value = $value;
  2584. $row_min_time = $timestamp;
  2585. $rh{$runtime_string} = $runtime_string."|".$min_value."|".$row_min_time;
  2586. }
  2587. $i++;
  2588. }
  2589. #---------------------------------------------------------------------------------------------
  2590. Log3 ($name, 5, "DbRep $name - result of minValue calculation before encoding:");
  2591. foreach my $key (sort(keys(%rh))) {
  2592. Log3 ($name, 5, "runtimestring Key: $key, value: ".$rh{$key});
  2593. }
  2594. # Ergebnishash als Einzeiler zurückgeben bzw. an Schreibroutine übergeben
  2595. my $rows = join('§', %rh);
  2596. # Ergebnisse in Datenbank schreiben
  2597. my ($wrt,$irowdone);
  2598. if($prop =~ /writeToDB/) {
  2599. ($wrt,$irowdone,$err) = DbRep_OutputWriteToDB($name,$device,$reading,$rows,"min");
  2600. if ($err) {
  2601. Log3 $hash->{NAME}, 2, "DbRep $name - $err";
  2602. $err = encode_base64($err,"");
  2603. return "$name|''|$device|$reading|''|$err|''";
  2604. }
  2605. $rt = $rt+$wrt;
  2606. }
  2607. my $rowlist = encode_base64($rows,"");
  2608. # Background-Laufzeit ermitteln
  2609. my $brt = tv_interval($bst);
  2610. $rt = $rt.",".$brt;
  2611. return "$name|$rowlist|$device|$reading|$rt|0|$irowdone";
  2612. }
  2613. ####################################################################################################
  2614. # Auswertungsroutine der nichtblockierenden DB-Abfrage minValue
  2615. ####################################################################################################
  2616. sub minval_ParseDone($) {
  2617. my ($string) = @_;
  2618. my @a = split("\\|",$string);
  2619. my $hash = $defs{$a[0]};
  2620. my $name = $hash->{NAME};
  2621. my $rowlist = decode_base64($a[1]);
  2622. my $device = $a[2];
  2623. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2624. my $reading = $a[3];
  2625. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2626. my $bt = $a[4];
  2627. my ($rt,$brt) = split(",", $bt);
  2628. my $err = $a[5]?decode_base64($a[5]):undef;
  2629. my $irowdone = $a[6];
  2630. my $reading_runtime_string;
  2631. if ($err) {
  2632. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2633. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2634. delete($hash->{HELPER}{RUNNING_PID});
  2635. return;
  2636. }
  2637. my %rh = split("§", $rowlist);
  2638. Log3 ($name, 5, "DbRep $name - result of minValue calculation after decoding:");
  2639. foreach my $key (sort(keys(%rh))) {
  2640. Log3 ($name, 5, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  2641. }
  2642. # Readingaufbereitung
  2643. readingsBeginUpdate($hash);
  2644. # only for this block because of warnings if details of readings are not set
  2645. no warnings 'uninitialized';
  2646. foreach my $key (sort(keys(%rh))) {
  2647. my @k = split("\\|",$rh{$key});
  2648. my $rsf = $k[2]."__" if($k[2]);
  2649. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2650. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  2651. } else {
  2652. my $ds = $device."__" if ($device);
  2653. my $rds = $reading."__" if ($reading);
  2654. $reading_runtime_string = $rsf.$ds.$rds."MIN__".$k[0];
  2655. }
  2656. my $rv = $k[1];
  2657. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, defined($rv)?sprintf("%.4f",$rv):"-");
  2658. }
  2659. ReadingsBulkUpdateValue ($hash, "db_lines_processed", $irowdone) if($hash->{LASTCMD} =~ /writeToDB/);
  2660. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  2661. readingsEndUpdate($hash, 1);
  2662. delete($hash->{HELPER}{RUNNING_PID});
  2663. return;
  2664. }
  2665. ####################################################################################################
  2666. # nichtblockierende DB-Abfrage diffValue
  2667. ####################################################################################################
  2668. sub diffval_DoParse($) {
  2669. my ($string) = @_;
  2670. my ($name,$device,$reading,$prop,$ts) = split("\\§", $string);
  2671. my $hash = $defs{$name};
  2672. my $dbloghash = $hash->{dbloghash};
  2673. my $dbconn = $dbloghash->{dbconn};
  2674. my $dbuser = $dbloghash->{dbuser};
  2675. my $dblogname = $dbloghash->{NAME};
  2676. my $dbmodel = $dbloghash->{MODEL};
  2677. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2678. my ($dbh,$sql,$sth,$err,$selspec);
  2679. # Background-Startzeit
  2680. my $bst = [gettimeofday];
  2681. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2682. if ($@) {
  2683. $err = encode_base64($@,"");
  2684. Log3 ($name, 2, "DbRep $name - $@");
  2685. return "$name|''|$device|$reading|''|''|''|$err|''";
  2686. }
  2687. # only for this block because of warnings if details of readings are not set
  2688. no warnings 'uninitialized';
  2689. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2690. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  2691. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2692. # Timestampstring to Array
  2693. my @ts = split("\\|", $ts);
  2694. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2695. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  2696. if($dbmodel eq "MYSQL") {
  2697. $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 ";
  2698. } else {
  2699. $selspec = "TIMESTAMP,VALUE";
  2700. }
  2701. # SQL-Startzeit
  2702. my $st = [gettimeofday];
  2703. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  2704. my @row_array;
  2705. my @array;
  2706. foreach my $row (@ts) {
  2707. my @a = split("#", $row);
  2708. my $runtime_string = $a[0];
  2709. my $runtime_string_first = $a[1];
  2710. my $runtime_string_next = $a[2];
  2711. $runtime_string = encode_base64($runtime_string,"");
  2712. if($dbmodel eq "MYSQL") {
  2713. eval {$dbh->do("set \@V:= 0, \@diff:= 0, \@diffTotal:= 0, \@RB:= 1;");}; # @\RB = Resetbit wenn neues Selektionsintervall beginnt
  2714. }
  2715. if ($@) {
  2716. $err = encode_base64($@,"");
  2717. Log3 ($name, 2, "DbRep $name - $@");
  2718. $dbh->disconnect;
  2719. return "$name|''|$device|$reading|''|''|''|$err|''";
  2720. }
  2721. if ($IsTimeSet || $IsAggrSet) {
  2722. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  2723. } else {
  2724. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,'');
  2725. }
  2726. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  2727. eval{ $sth = $dbh->prepare($sql);
  2728. $sth->execute();
  2729. };
  2730. if ($@) {
  2731. $err = encode_base64($@,"");
  2732. Log3 ($name, 2, "DbRep $name - $@");
  2733. $dbh->disconnect;
  2734. return "$name|''|$device|$reading|''|''|''|$err|''";
  2735. } else {
  2736. if($dbmodel eq "MYSQL") {
  2737. @array = map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]." ".$_ -> [2]."\n" } @{ $sth->fetchall_arrayref() };
  2738. } else {
  2739. @array = map { $runtime_string." ".$_ -> [0]." ".$_ -> [1]."\n" } @{ $sth->fetchall_arrayref() };
  2740. if (@array) {
  2741. my @sp;
  2742. my $dse = 0;
  2743. my $vold;
  2744. my @sqlite_array;
  2745. foreach my $row (@array) {
  2746. @sp = split("[ \t][ \t]*", $row, 4);
  2747. my $runtime_string = $sp[0];
  2748. my $timestamp = $sp[2]?$sp[1]." ".$sp[2]:$sp[1];
  2749. my $vnew = $sp[3];
  2750. $vnew =~ tr/\n//d;
  2751. $dse = ($vold && (($vnew-$vold) > 0))?($vnew-$vold):0;
  2752. @sp = $runtime_string." ".$timestamp." ".$vnew." ".$dse."\n";
  2753. $vold = $vnew;
  2754. push(@sqlite_array, @sp);
  2755. }
  2756. @array = @sqlite_array;
  2757. }
  2758. }
  2759. if(!@array) {
  2760. if(AttrVal($name, "aggregation", "") eq "hour") {
  2761. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  2762. @array = ($runtime_string." ".$rsf[0]."_".$rsf[1]."\n");
  2763. } else {
  2764. my @rsf = split(" ",$runtime_string_first);
  2765. @array = ($runtime_string." ".$rsf[0]."\n");
  2766. }
  2767. }
  2768. push(@row_array, @array);
  2769. }
  2770. }
  2771. # SQL-Laufzeit ermitteln
  2772. my $rt = tv_interval($st);
  2773. $dbh->disconnect;
  2774. Log3 ($name, 5, "DbRep $name - raw data of row_array result:\n @row_array");
  2775. my $difflimit = AttrVal($name, "diffAccept", "20"); # legt fest, bis zu welchem Wert Differenzen akzeptiert werden (Ausreißer eliminieren)
  2776. # Berechnung diffValue aus Selektionshash
  2777. my %rh = (); # Ergebnishash, wird alle Ergebniszeilen enthalten
  2778. my %ch = (); # counthash, enthält die Anzahl der verarbeiteten Datasets pro runtime_string
  2779. my $lastruntimestring;
  2780. my $i = 1;
  2781. my $lval; # immer der letzte Wert von $value
  2782. my $rslval; # runtimestring von lval
  2783. my $uediff; # Übertragsdifferenz (Differenz zwischen letzten Wert einer Aggregationsperiode und dem ersten Wert der Folgeperiode)
  2784. my $diff_current; # Differenzwert des aktuellen Datasets
  2785. my $diff_before; # Differenzwert vorheriger Datensatz
  2786. my $rejectstr; # String der ignorierten Differenzsätze
  2787. my $diff_total; # Summenwert aller berücksichtigten Teildifferenzen
  2788. my $max = ($#row_array)+1; # Anzahl aller Listenelemente
  2789. Log3 ($name, 5, "DbRep $name - data of row_array result assigned to fields:\n");
  2790. foreach my $row (@row_array) {
  2791. my @a = split("[ \t][ \t]*", $row, 6);
  2792. my $runtime_string = decode_base64($a[0]);
  2793. $lastruntimestring = $runtime_string if ($i == 1);
  2794. my $timestamp = $a[2]?$a[1]."_".$a[2]:$a[1];
  2795. my $value = $a[3]?$a[3]:0;
  2796. my $diff = $a[4]?sprintf("%.4f",$a[4]):0;
  2797. # if ($uediff) {
  2798. # $diff = $diff + $uediff;
  2799. # Log3 ($name, 4, "DbRep $name - balance difference of $uediff between $rslval and $runtime_string");
  2800. # $uediff = 0;
  2801. # }
  2802. # Leerzeichen am Ende $timestamp entfernen
  2803. $timestamp =~ s/\s+$//g;
  2804. # Test auf $value = "numeric"
  2805. if (!looks_like_number($value)) {
  2806. $a[3] =~ s/\s+$//g;
  2807. Log3 ($name, 2, "DbRep $name - ERROR - value isn't numeric in diffValue function. Faulty dataset was \nTIMESTAMP: $timestamp, DEVICE: $device, READING: $reading, VALUE: $value.");
  2808. $err = encode_base64("Value isn't numeric. Faulty dataset was - TIMESTAMP: $timestamp, VALUE: $value", "");
  2809. return "$name|''|$device|$reading|''|''|''|$err|''";
  2810. }
  2811. Log3 ($name, 5, "DbRep $name - Runtimestring: $runtime_string, DEVICE: $device, READING: $reading, \nTIMESTAMP: $timestamp, VALUE: $value, DIFF: $diff");
  2812. # String ignorierter Zeilen erzeugen
  2813. $diff_current = $timestamp." ".$diff;
  2814. if($diff > $difflimit) {
  2815. $rejectstr .= $diff_before." -> ".$diff_current."\n";
  2816. }
  2817. $diff_before = $diff_current;
  2818. # Ergebnishash erzeugen
  2819. if ($runtime_string eq $lastruntimestring) {
  2820. if ($i == 1) {
  2821. $diff_total = $diff?$diff:0 if($diff <= $difflimit);
  2822. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2823. $ch{$runtime_string} = 1 if($value);
  2824. $lval = $value;
  2825. $rslval = $runtime_string;
  2826. }
  2827. if ($diff) {
  2828. if($diff <= $difflimit) {
  2829. $diff_total = $diff_total+$diff;
  2830. }
  2831. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2832. $ch{$runtime_string}++ if($value && $i > 1);
  2833. $lval = $value;
  2834. $rslval = $runtime_string;
  2835. }
  2836. } else {
  2837. # neuer Zeitabschnitt beginnt, ersten Value-Wert erfassen und Übertragsdifferenz bilden
  2838. $lastruntimestring = $runtime_string;
  2839. $i = 1;
  2840. $uediff = $value - $lval if($value > $lval);
  2841. $diff = $uediff;
  2842. $lval = $value if($value); # Übetrag über Perioden mit value = 0 hinweg !
  2843. $rslval = $runtime_string;
  2844. Log3 ($name, 4, "DbRep $name - balance difference of $uediff between $rslval and $runtime_string");
  2845. $diff_total = $diff?$diff:0 if($diff <= $difflimit);
  2846. $rh{$runtime_string} = $runtime_string."|".$diff_total."|".$timestamp;
  2847. $ch{$runtime_string} = 1 if($value);
  2848. $uediff = 0;
  2849. }
  2850. $i++;
  2851. }
  2852. Log3 ($name, 4, "DbRep $name - result of diffValue calculation before encoding:");
  2853. foreach my $key (sort(keys(%rh))) {
  2854. Log3 ($name, 4, "runtimestring Key: $key, value: ".$rh{$key});
  2855. }
  2856. my $ncp = DbRep_calcount($hash,\%ch);
  2857. my ($ncps,$ncpslist);
  2858. if(%$ncp) {
  2859. Log3 ($name, 3, "DbRep $name - time/aggregation periods containing only one dataset -> no diffValue calc was possible in period:");
  2860. foreach my $key (sort(keys%{$ncp})) {
  2861. Log3 ($name, 3, $key) ;
  2862. }
  2863. $ncps = join('§', %$ncp);
  2864. $ncpslist = encode_base64($ncps,"");
  2865. }
  2866. # Ergebnishash als Einzeiler zurückgeben
  2867. # ignorierte Zeilen ($diff > $difflimit)
  2868. my $rowsrej = encode_base64($rejectstr,"") if($rejectstr);
  2869. # Ergebnishash
  2870. my $rows = join('§', %rh);
  2871. # Ergebnisse in Datenbank schreiben
  2872. my ($wrt,$irowdone);
  2873. if($prop =~ /writeToDB/) {
  2874. ($wrt,$irowdone,$err) = DbRep_OutputWriteToDB($name,$device,$reading,$rows,"diff");
  2875. if ($err) {
  2876. Log3 $hash->{NAME}, 2, "DbRep $name - $err";
  2877. $err = encode_base64($err,"");
  2878. return "$name|''|$device|$reading|''|''|''|$err|''";
  2879. }
  2880. $rt = $rt+$wrt;
  2881. }
  2882. my $rowlist = encode_base64($rows,"");
  2883. # Background-Laufzeit ermitteln
  2884. my $brt = tv_interval($bst);
  2885. $rt = $rt.",".$brt;
  2886. return "$name|$rowlist|$device|$reading|$rt|$rowsrej|$ncpslist|0|$irowdone";
  2887. }
  2888. ####################################################################################################
  2889. # Auswertungsroutine der nichtblockierenden DB-Abfrage diffValue
  2890. ####################################################################################################
  2891. sub diffval_ParseDone($) {
  2892. my ($string) = @_;
  2893. my @a = split("\\|",$string);
  2894. my $hash = $defs{$a[0]};
  2895. my $name = $hash->{NAME};
  2896. my $rowlist = decode_base64($a[1]);
  2897. my $device = $a[2];
  2898. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2899. my $reading = $a[3];
  2900. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  2901. my $bt = $a[4];
  2902. my ($rt,$brt) = split(",", $bt);
  2903. my $rowsrej = $a[5]?decode_base64($a[5]):undef; # String von Datensätzen die nicht berücksichtigt wurden (diff Schwellenwert Überschreitung)
  2904. my $ncpslist = decode_base64($a[6]); # Hash von Perioden die nicht kalkuliert werden konnten "no calc in period"
  2905. my $err = $a[7]?decode_base64($a[7]):undef;
  2906. my $irowdone = $a[8];
  2907. my $reading_runtime_string;
  2908. my $difflimit = AttrVal($name, "diffAccept", "20"); # legt fest, bis zu welchem Wert Differenzen akzeptoert werden (Ausreißer eliminieren)AttrVal($name, "diffAccept", "20");
  2909. if ($err) {
  2910. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  2911. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  2912. delete($hash->{HELPER}{RUNNING_PID});
  2913. return;
  2914. }
  2915. # only for this block because of warnings if details of readings are not set
  2916. no warnings 'uninitialized';
  2917. # Auswertung hashes für state-Warning
  2918. $rowsrej =~ s/_/ /g;
  2919. Log3 ($name, 3, "DbRep $name -> data ignored while calc diffValue due to threshold overrun (diffAccept = $difflimit): \n$rowsrej")
  2920. if($rowsrej);
  2921. $rowsrej =~ s/\n/ \|\| /g;
  2922. my %ncp = split("§", $ncpslist);
  2923. my $ncpstr;
  2924. if(%ncp) {
  2925. foreach my $ncpkey (sort(keys(%ncp))) {
  2926. $ncpstr .= $ncpkey." || ";
  2927. }
  2928. }
  2929. # Readingaufbereitung
  2930. my %rh = split("§", $rowlist);
  2931. Log3 ($name, 4, "DbRep $name - result of diffValue calculation after decoding:");
  2932. foreach my $key (sort(keys(%rh))) {
  2933. Log3 ($name, 4, "DbRep $name - runtimestring Key: $key, value: ".$rh{$key});
  2934. }
  2935. readingsBeginUpdate($hash);
  2936. foreach my $key (sort(keys(%rh))) {
  2937. my @k = split("\\|",$rh{$key});
  2938. my $rts = $k[2]."__";
  2939. $rts =~ s/:/-/g; # substituieren unsupported characters -> siehe fhem.pl
  2940. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  2941. $reading_runtime_string = $rts.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$k[0];
  2942. } else {
  2943. my $ds = $device."__" if ($device);
  2944. my $rds = $reading."__" if ($reading);
  2945. $reading_runtime_string = $rts.$ds.$rds."DIFF__".$k[0];
  2946. }
  2947. my $rv = $k[1];
  2948. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $rv?sprintf("%.4f",$rv):"-");
  2949. }
  2950. ReadingsBulkUpdateValue ($hash, "db_lines_processed", $irowdone) if($hash->{LASTCMD} =~ /writeToDB/);
  2951. ReadingsBulkUpdateValue ($hash, "diff_overrun_limit_".$difflimit, $rowsrej) if($rowsrej);
  2952. ReadingsBulkUpdateValue ($hash, "less_data_in_period", $ncpstr) if($ncpstr);
  2953. ReadingsBulkUpdateTimeState($hash,$brt,$rt,($ncpstr||$rowsrej)?"Warning":"done");
  2954. readingsEndUpdate($hash, 1);
  2955. delete($hash->{HELPER}{RUNNING_PID});
  2956. return;
  2957. }
  2958. ####################################################################################################
  2959. # nichtblockierende DB-Abfrage sumValue
  2960. ####################################################################################################
  2961. sub sumval_DoParse($) {
  2962. my ($string) = @_;
  2963. my ($name,$device,$reading,$prop,$ts) = split("\\§", $string);
  2964. my $hash = $defs{$name};
  2965. my $dbloghash = $hash->{dbloghash};
  2966. my $dbconn = $dbloghash->{dbconn};
  2967. my $dbuser = $dbloghash->{dbuser};
  2968. my $dblogname = $dbloghash->{NAME};
  2969. my $dbpassword = $attr{"sec$dblogname"}{secret};
  2970. my ($dbh,$sql,$sth,$err,$selspec);
  2971. # Background-Startzeit
  2972. my $bst = [gettimeofday];
  2973. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  2974. if ($@) {
  2975. $err = encode_base64($@,"");
  2976. Log3 ($name, 2, "DbRep $name - $@");
  2977. return "$name|''|$device|$reading|''|$err|''";
  2978. }
  2979. # only for this block because of warnings if details of readings are not set
  2980. no warnings 'uninitialized';
  2981. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  2982. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  2983. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  2984. # Timestampstring to Array
  2985. my @ts = split("\\|", $ts);
  2986. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  2987. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  2988. if ($dbloghash->{MODEL} eq "POSTGRESQL") {
  2989. $selspec = "SUM(VALUE::numeric)";
  2990. } elsif ($dbloghash->{MODEL} eq "MYSQL") {
  2991. $selspec = "SUM(VALUE)";
  2992. } elsif ($dbloghash->{MODEL} eq "SQLITE") {
  2993. $selspec = "SUM(VALUE)";
  2994. } else {
  2995. $selspec = "SUM(VALUE)";
  2996. }
  2997. # SQL-Startzeit
  2998. my $st = [gettimeofday];
  2999. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  3000. my $arrstr;
  3001. foreach my $row (@ts) {
  3002. my @a = split("#", $row);
  3003. my $runtime_string = $a[0];
  3004. my $runtime_string_first = $a[1];
  3005. my $runtime_string_next = $a[2];
  3006. if ($IsTimeSet || $IsAggrSet) {
  3007. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  3008. } else {
  3009. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,'');
  3010. }
  3011. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3012. eval{ $sth = $dbh->prepare($sql);
  3013. $sth->execute();
  3014. };
  3015. if ($@) {
  3016. $err = encode_base64($@,"");
  3017. Log3 ($name, 2, "DbRep $name - $@");
  3018. $dbh->disconnect;
  3019. return "$name|''|$device|$reading|''|$err|''";
  3020. }
  3021. # DB-Abfrage -> Ergebnis in @arr aufnehmen
  3022. my @line = $sth->fetchrow_array();
  3023. Log3 ($name, 5, "DbRep $name - SQL result: $line[0]") if($line[0]);
  3024. if(AttrVal($name, "aggregation", "") eq "hour") {
  3025. my @rsf = split(/[" "\|":"]/,$runtime_string_first);
  3026. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."_".$rsf[1]."|";
  3027. } else {
  3028. my @rsf = split(" ",$runtime_string_first);
  3029. $arrstr .= $runtime_string."#".$line[0]."#".$rsf[0]."|";
  3030. }
  3031. }
  3032. $sth->finish;
  3033. $dbh->disconnect;
  3034. # SQL-Laufzeit ermitteln
  3035. my $rt = tv_interval($st);
  3036. # Ergebnisse in Datenbank schreiben
  3037. my ($wrt,$irowdone);
  3038. if($prop =~ /writeToDB/) {
  3039. ($wrt,$irowdone,$err) = DbRep_OutputWriteToDB($name,$device,$reading,$arrstr,"sum");
  3040. if ($err) {
  3041. Log3 $hash->{NAME}, 2, "DbRep $name - $err";
  3042. $err = encode_base64($err,"");
  3043. return "$name|''|$device|$reading|''|$err|''";
  3044. }
  3045. $rt = $rt+$wrt;
  3046. }
  3047. # Daten müssen als Einzeiler zurückgegeben werden
  3048. $arrstr = encode_base64($arrstr,"");
  3049. # Background-Laufzeit ermitteln
  3050. my $brt = tv_interval($bst);
  3051. $rt = $rt.",".$brt;
  3052. return "$name|$arrstr|$device|$reading|$rt|0|$irowdone";
  3053. }
  3054. ####################################################################################################
  3055. # Auswertungsroutine der nichtblockierenden DB-Abfrage sumValue
  3056. ####################################################################################################
  3057. sub sumval_ParseDone($) {
  3058. my ($string) = @_;
  3059. my @a = split("\\|",$string);
  3060. my $hash = $defs{$a[0]};
  3061. my $name = $hash->{NAME};
  3062. my $arrstr = decode_base64($a[1]);
  3063. my $device = $a[2];
  3064. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3065. my $reading = $a[3];
  3066. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3067. my $bt = $a[4];
  3068. my ($rt,$brt) = split(",", $bt);
  3069. my $err = $a[5]?decode_base64($a[5]):undef;
  3070. my $irowdone = $a[6];
  3071. my $reading_runtime_string;
  3072. if ($err) {
  3073. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3074. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3075. delete($hash->{HELPER}{RUNNING_PID});
  3076. return;
  3077. }
  3078. # only for this block because of warnings if details of readings are not set
  3079. no warnings 'uninitialized';
  3080. # Readingaufbereitung
  3081. readingsBeginUpdate($hash);
  3082. my @arr = split("\\|", $arrstr);
  3083. foreach my $row (@arr) {
  3084. my @a = split("#", $row);
  3085. my $runtime_string = $a[0];
  3086. my $c = $a[1];
  3087. my $rsf = $a[2]."__";
  3088. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  3089. $reading_runtime_string = $rsf.AttrVal($hash->{NAME}, "readingNameMap", "")."__".$runtime_string;
  3090. } else {
  3091. my $ds = $device."__" if ($device);
  3092. my $rds = $reading."__" if ($reading);
  3093. $reading_runtime_string = $rsf.$ds.$rds."SUM__".$runtime_string;
  3094. }
  3095. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $c?sprintf("%.4f",$c):"-");
  3096. }
  3097. ReadingsBulkUpdateValue ($hash, "db_lines_processed", $irowdone) if($hash->{LASTCMD} =~ /writeToDB/);
  3098. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3099. readingsEndUpdate($hash, 1);
  3100. delete($hash->{HELPER}{RUNNING_PID});
  3101. return;
  3102. }
  3103. ####################################################################################################
  3104. # nichtblockierendes DB delete
  3105. ####################################################################################################
  3106. sub del_DoParse($) {
  3107. my ($string) = @_;
  3108. my ($name,$table,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  3109. my $hash = $defs{$name};
  3110. my $dbloghash = $hash->{dbloghash};
  3111. my $dbconn = $dbloghash->{dbconn};
  3112. my $dbuser = $dbloghash->{dbuser};
  3113. my $dblogname = $dbloghash->{NAME};
  3114. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3115. my ($dbh,$sql,$sth,$err,$rows);
  3116. # Background-Startzeit
  3117. my $bst = [gettimeofday];
  3118. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
  3119. if ($@) {
  3120. $err = encode_base64($@,"");
  3121. Log3 ($name, 2, "DbRep $name - $@");
  3122. return "$name|''|''|$err|''|''|''";
  3123. }
  3124. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  3125. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  3126. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  3127. # SQL zusammenstellen für DB-Operation
  3128. if ($IsTimeSet || $IsAggrSet) {
  3129. $sql = DbRep_createDeleteSql($hash,$table,$device,$reading,$runtime_string_first,$runtime_string_next,'');
  3130. } else {
  3131. $sql = DbRep_createDeleteSql($hash,$table,$device,$reading,undef,undef,'');
  3132. }
  3133. $sth = $dbh->prepare($sql);
  3134. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3135. # SQL-Startzeit
  3136. my $st = [gettimeofday];
  3137. eval {$sth->execute();};
  3138. if ($@) {
  3139. $err = encode_base64($@,"");
  3140. Log3 ($name, 2, "DbRep $name - $@");
  3141. $dbh->disconnect;
  3142. return "$name|''|''|$err|''|''|''";
  3143. }
  3144. $rows = $sth->rows;
  3145. $dbh->commit() if(!$dbh->{AutoCommit});
  3146. $dbh->disconnect;
  3147. # SQL-Laufzeit ermitteln
  3148. my $rt = tv_interval($st);
  3149. Log3 ($name, 5, "DbRep $name - Number of deleted rows: $rows");
  3150. # Background-Laufzeit ermitteln
  3151. my $brt = tv_interval($bst);
  3152. $rt = $rt.",".$brt;
  3153. return "$name|$rows|$rt|0|$table|$device|$reading";
  3154. }
  3155. ####################################################################################################
  3156. # Auswertungsroutine DB delete
  3157. ####################################################################################################
  3158. sub del_ParseDone($) {
  3159. my ($string) = @_;
  3160. my @a = split("\\|",$string);
  3161. my $hash = $defs{$a[0]};
  3162. my $name = $hash->{NAME};
  3163. my $rows = $a[1];
  3164. my $bt = $a[2];
  3165. my ($rt,$brt) = split(",", $bt);
  3166. my $err = $a[3]?decode_base64($a[3]):undef;
  3167. my $table = $a[4];
  3168. my $device = $a[5];
  3169. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3170. my $reading = $a[6];
  3171. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  3172. if ($err) {
  3173. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3174. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3175. delete($hash->{HELPER}{RUNNING_PID});
  3176. return;
  3177. }
  3178. # only for this block because of warnings if details of readings are not set
  3179. no warnings 'uninitialized';
  3180. my ($reading_runtime_string, $ds, $rds);
  3181. if (AttrVal($hash->{NAME}, "readingNameMap", "")) {
  3182. $reading_runtime_string = AttrVal($hash->{NAME}, "readingNameMap", "")."--DELETED_ROWS--";
  3183. } else {
  3184. $ds = $device."--" if ($device && $table ne "current");
  3185. $rds = $reading."--" if ($reading && $table ne "current");
  3186. $reading_runtime_string = $ds.$rds."--DELETED_ROWS_".uc($table)."--";
  3187. }
  3188. readingsBeginUpdate($hash);
  3189. ReadingsBulkUpdateValue ($hash, $reading_runtime_string, $rows);
  3190. $rows = ($table eq "current")?$rows:$ds.$rds.$rows;
  3191. Log3 ($name, 3, "DbRep $name - Entries of $hash->{DATABASE}.$table deleted: $rows");
  3192. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3193. readingsEndUpdate($hash, 1);
  3194. delete($hash->{HELPER}{RUNNING_PID});
  3195. return;
  3196. }
  3197. ####################################################################################################
  3198. # nichtblockierendes DB insert
  3199. ####################################################################################################
  3200. sub insert_Push($) {
  3201. my ($name) = @_;
  3202. my $hash = $defs{$name};
  3203. my $dbloghash = $hash->{dbloghash};
  3204. my $dbconn = $dbloghash->{dbconn};
  3205. my $dbuser = $dbloghash->{dbuser};
  3206. my $dblogname = $dbloghash->{NAME};
  3207. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3208. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3209. my ($err,$sth);
  3210. # Background-Startzeit
  3211. my $bst = [gettimeofday];
  3212. my $dbh;
  3213. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3214. if ($@) {
  3215. $err = encode_base64($@,"");
  3216. Log3 ($name, 2, "DbRep $name - $@");
  3217. return "$name|''|''|$err";
  3218. }
  3219. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  3220. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh);
  3221. my $i_timestamp = $hash->{HELPER}{I_TIMESTAMP};
  3222. my $i_device = $hash->{HELPER}{I_DEVICE};
  3223. my $i_type = $hash->{HELPER}{I_TYPE};
  3224. my $i_event = $hash->{HELPER}{I_EVENT};
  3225. my $i_reading = $hash->{HELPER}{I_READING};
  3226. my $i_value = $hash->{HELPER}{I_VALUE};
  3227. my $i_unit = $hash->{HELPER}{I_UNIT} ? $hash->{HELPER}{I_UNIT} : " ";
  3228. # SQL zusammenstellen für DB-Operation
  3229. 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");
  3230. # SQL-Startzeit
  3231. my $st = [gettimeofday];
  3232. # insert history mit/ohne primary key
  3233. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  3234. eval { $sth = $dbh->prepare("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3235. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  3236. eval { $sth = $dbh->prepare("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3237. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  3238. eval { $sth = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  3239. } else {
  3240. eval { $sth = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  3241. }
  3242. if ($@) {
  3243. $err = encode_base64($@,"");
  3244. Log3 ($name, 2, "DbRep $name - $@");
  3245. $dbh->disconnect();
  3246. return "$name|''|''|$err";
  3247. }
  3248. $dbh->begin_work();
  3249. eval {$sth->execute($i_timestamp, $i_device, $i_type, $i_event, $i_reading, $i_value, $i_unit);};
  3250. my $irow;
  3251. if ($@) {
  3252. $err = encode_base64($@,"");
  3253. Log3 ($name, 2, "DbRep $name - Insert new dataset into database failed".($usepkh?" (possible PK violation) ":": ")."$@");
  3254. $dbh->rollback();
  3255. $dbh->disconnect();
  3256. return "$name|''|''|$err";
  3257. } else {
  3258. $dbh->commit();
  3259. $irow = $sth->rows;
  3260. $dbh->disconnect();
  3261. }
  3262. # SQL-Laufzeit ermitteln
  3263. my $rt = tv_interval($st);
  3264. # Background-Laufzeit ermitteln
  3265. my $brt = tv_interval($bst);
  3266. $rt = $rt.",".$brt;
  3267. return "$name|$irow|$rt|0";
  3268. }
  3269. ####################################################################################################
  3270. # Auswertungsroutine DB insert
  3271. ####################################################################################################
  3272. sub insert_Done($) {
  3273. my ($string) = @_;
  3274. my @a = split("\\|",$string);
  3275. my $hash = $defs{$a[0]};
  3276. my $name = $hash->{NAME};
  3277. my $irow = $a[1];
  3278. my $bt = $a[2];
  3279. my ($rt,$brt) = split(",", $bt);
  3280. my $err = $a[3]?decode_base64($a[3]):undef;
  3281. my $i_timestamp = delete $hash->{HELPER}{I_TIMESTAMP};
  3282. my $i_device = delete $hash->{HELPER}{I_DEVICE};
  3283. my $i_type = delete $hash->{HELPER}{I_TYPE};
  3284. my $i_event = delete $hash->{HELPER}{I_EVENT};
  3285. my $i_reading = delete $hash->{HELPER}{I_READING};
  3286. my $i_value = delete $hash->{HELPER}{I_VALUE};
  3287. my $i_unit = delete $hash->{HELPER}{I_UNIT};
  3288. if ($err) {
  3289. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3290. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3291. delete($hash->{HELPER}{RUNNING_PID});
  3292. return;
  3293. }
  3294. # only for this block because of warnings if details of readings are not set
  3295. no warnings 'uninitialized';
  3296. readingsBeginUpdate($hash);
  3297. ReadingsBulkUpdateValue ($hash, "number_lines_inserted", $irow);
  3298. ReadingsBulkUpdateValue ($hash, "data_inserted", $i_timestamp.", ".$i_device.", ".$i_type.", ".$i_event.", ".$i_reading.", ".$i_value.", ".$i_unit);
  3299. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3300. readingsEndUpdate($hash, 1);
  3301. 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");
  3302. delete($hash->{HELPER}{RUNNING_PID});
  3303. return;
  3304. }
  3305. ####################################################################################################
  3306. # Current-Tabelle mit Device,Reading Kombinationen aus history auffüllen
  3307. ####################################################################################################
  3308. sub currentfillup_Push($) {
  3309. my ($string) = @_;
  3310. my ($name,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  3311. my $hash = $defs{$name};
  3312. my $dbloghash = $hash->{dbloghash};
  3313. my $dbconn = $dbloghash->{dbconn};
  3314. my $dbuser = $dbloghash->{dbuser};
  3315. my $dblogname = $dbloghash->{NAME};
  3316. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3317. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3318. my ($err,$sth,$sql,$devs,$danz,$ranz);
  3319. # Background-Startzeit
  3320. my $bst = [gettimeofday];
  3321. my $dbh;
  3322. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3323. if ($@) {
  3324. $err = encode_base64($@,"");
  3325. Log3 ($name, 2, "DbRep $name - $@");
  3326. return "$name|''|''|$err|''|''";
  3327. }
  3328. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  3329. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh);
  3330. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  3331. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  3332. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  3333. ($devs,$danz,$reading,$ranz) = DbRep_specsForSql($hash,$device,$reading);
  3334. # SQL-Startzeit
  3335. my $st = [gettimeofday];
  3336. # insert history mit/ohne primary key
  3337. # SQL zusammenstellen für DB-Operation
  3338. if ($usepkc && $dbloghash->{MODEL} eq 'MYSQL') {
  3339. $sql = "INSERT IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  3340. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  3341. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  3342. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  3343. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  3344. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  3345. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  3346. if ($IsTimeSet) {
  3347. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  3348. } else {
  3349. $sql .= "1 ";
  3350. }
  3351. $sql .= "group by timestamp,device,reading ;";
  3352. } elsif ($usepkc && $dbloghash->{MODEL} eq 'SQLITE') {
  3353. $sql = "INSERT OR IGNORE INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  3354. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  3355. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  3356. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  3357. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  3358. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  3359. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  3360. if ($IsTimeSet) {
  3361. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  3362. } else {
  3363. $sql .= "1 ";
  3364. }
  3365. $sql .= "group by timestamp,device,reading ;";
  3366. } elsif ($usepkc && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  3367. $sql = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where ";
  3368. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  3369. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  3370. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  3371. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  3372. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  3373. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  3374. if ($IsTimeSet) {
  3375. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  3376. } else {
  3377. $sql .= "true ";
  3378. }
  3379. $sql .= "group by device,reading ON CONFLICT ($pkc) DO NOTHING; ";
  3380. } else {
  3381. if($dbloghash->{MODEL} ne 'POSTGRESQL') {
  3382. # MySQL und SQLite
  3383. $sql = "INSERT INTO current (TIMESTAMP,DEVICE,READING) SELECT timestamp,device,reading FROM history where ";
  3384. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  3385. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  3386. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  3387. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  3388. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  3389. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  3390. if ($IsTimeSet) {
  3391. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  3392. } else {
  3393. $sql .= "1 ";
  3394. }
  3395. $sql .= "group by device,reading ;";
  3396. } else {
  3397. # PostgreSQL
  3398. $sql = "INSERT INTO current (DEVICE,TIMESTAMP,READING) SELECT device, (array_agg(timestamp ORDER BY reading ASC))[1], reading FROM history where ";
  3399. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  3400. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  3401. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  3402. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  3403. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  3404. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  3405. if ($IsTimeSet) {
  3406. $sql .= "TIMESTAMP >= '$runtime_string_first' AND TIMESTAMP < '$runtime_string_next' ";
  3407. } else {
  3408. $sql .= "true ";
  3409. }
  3410. $sql .= "group by device,reading;";
  3411. }
  3412. }
  3413. # Log SQL Statement
  3414. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3415. eval { $sth = $dbh->prepare($sql); };
  3416. if ($@) {
  3417. $err = encode_base64($@,"");
  3418. Log3 ($name, 2, "DbRep $name - $@");
  3419. $dbh->disconnect();
  3420. return "$name|''|''|$err|''|''";
  3421. }
  3422. my $irow;
  3423. $dbh->begin_work();
  3424. eval {$sth->execute();};
  3425. if ($@) {
  3426. $err = encode_base64($@,"");
  3427. Log3 ($name, 2, "DbRep $name - Insert new dataset into database failed".($usepkh?" (possible PK violation) ":": ")."$@");
  3428. $dbh->rollback();
  3429. $dbh->disconnect();
  3430. return "$name|''|''|$err|''|''";
  3431. } else {
  3432. $dbh->commit();
  3433. $irow = $sth->rows;
  3434. $dbh->disconnect();
  3435. }
  3436. # SQL-Laufzeit ermitteln
  3437. my $rt = tv_interval($st);
  3438. # Background-Laufzeit ermitteln
  3439. my $brt = tv_interval($bst);
  3440. $rt = $rt.",".$brt;
  3441. return "$name|$irow|$rt|0|$device|$reading";
  3442. }
  3443. ####################################################################################################
  3444. # Auswertungsroutine Current-Tabelle auffüllen
  3445. ####################################################################################################
  3446. sub currentfillup_Done($) {
  3447. my ($string) = @_;
  3448. my @a = split("\\|",$string);
  3449. my $hash = $defs{$a[0]};
  3450. my $name = $hash->{NAME};
  3451. my $irow = $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 $device = $a[4];
  3456. my $reading = $a[5];
  3457. undef $device if ($device =~ m(^%$));
  3458. undef $reading if ($reading =~ m(^%$));
  3459. if ($err) {
  3460. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3461. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3462. delete($hash->{HELPER}{RUNNING_PID});
  3463. return;
  3464. }
  3465. # only for this block because of warnings if details of readings are not set
  3466. no warnings 'uninitialized';
  3467. my $rowstr;
  3468. $rowstr = $irow if(!$device && !$reading);
  3469. $rowstr = $irow." - limited by device: ".$device if($device && !$reading);
  3470. $rowstr = $irow." - limited by reading: ".$reading if(!$device && $reading);
  3471. $rowstr = $irow." - limited by device: ".$device." and reading: ".$reading if($device && $reading);
  3472. readingsBeginUpdate($hash);
  3473. ReadingsBulkUpdateValue($hash, "number_lines_inserted", $rowstr);
  3474. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3475. readingsEndUpdate($hash, 1);
  3476. Log3 ($name, 3, "DbRep $name - Table '$hash->{DATABASE}'.'current' filled up with rows: $rowstr");
  3477. delete($hash->{HELPER}{RUNNING_PID});
  3478. return;
  3479. }
  3480. ####################################################################################################
  3481. # nichtblockierendes DB deviceRename / readingRename
  3482. ####################################################################################################
  3483. sub change_Push($) {
  3484. my ($string) = @_;
  3485. my ($name,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  3486. my $hash = $defs{$name};
  3487. my $dbloghash = $hash->{dbloghash};
  3488. my $dbconn = $dbloghash->{dbconn};
  3489. my $dbuser = $dbloghash->{dbuser};
  3490. my $dblogname = $dbloghash->{NAME};
  3491. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3492. my $table = "history";
  3493. my ($dbh,$err,$sql);
  3494. # Background-Startzeit
  3495. my $bst = [gettimeofday];
  3496. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
  3497. if ($@) {
  3498. $err = encode_base64($@,"");
  3499. Log3 ($name, 2, "DbRep $name - $@");
  3500. return "$name|''|''|$err";
  3501. }
  3502. my $renmode = $hash->{HELPER}{RENMODE};
  3503. # SQL-Startzeit
  3504. my $st = [gettimeofday];
  3505. my ($sth,$old,$new);
  3506. eval { $dbh->begin_work() if($dbh->{AutoCommit}); }; # Transaktion wenn gewünscht und autocommit ein
  3507. if ($@) {
  3508. Log3($name, 2, "DbRep $name -> Error start transaction - $@");
  3509. }
  3510. if ($renmode eq "devren") {
  3511. $old = delete $hash->{HELPER}{OLDDEV};
  3512. $new = delete $hash->{HELPER}{NEWDEV};
  3513. # SQL zusammenstellen für DB-Operation
  3514. Log3 ($name, 5, "DbRep $name -> Rename old device name \"$old\" to new device name \"$new\" in database $dblogname ");
  3515. # prepare DB operation
  3516. $old =~ s/'/''/g; # escape ' with ''
  3517. $new =~ s/'/''/g; # escape ' with ''
  3518. $sql = "UPDATE history SET TIMESTAMP=TIMESTAMP,DEVICE='$new' WHERE DEVICE='$old'; ";
  3519. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3520. $sth = $dbh->prepare($sql) ;
  3521. } elsif ($renmode eq "readren") {
  3522. $old = delete $hash->{HELPER}{OLDREAD};
  3523. $new = delete $hash->{HELPER}{NEWREAD};
  3524. # SQL zusammenstellen für DB-Operation
  3525. Log3 ($name, 5, "DbRep $name -> Rename old reading name \"$old\" to new reading name \"$new\" in database $dblogname ");
  3526. # prepare DB operation
  3527. $old =~ s/'/''/g; # escape ' with ''
  3528. $new =~ s/'/''/g; # escape ' with ''
  3529. $sql = "UPDATE history SET TIMESTAMP=TIMESTAMP,READING='$new' WHERE READING='$old'; ";
  3530. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3531. $sth = $dbh->prepare($sql) ;
  3532. }
  3533. $old =~ s/''/'/g; # escape back
  3534. $new =~ s/''/'/g; # escape back
  3535. my $urow;
  3536. eval { $sth->execute(); };
  3537. if ($@) {
  3538. $err = encode_base64($@,"");
  3539. my $m = ($renmode eq "devren")?"device":"reading";
  3540. Log3 ($name, 2, "DbRep $name - Failed to rename old $m name \"$old\" to new $m name \"$new\": $@");
  3541. $dbh->rollback() if(!$dbh->{AutoCommit});
  3542. $dbh->disconnect();
  3543. return "$name|''|''|$err";
  3544. } else {
  3545. $dbh->commit() if(!$dbh->{AutoCommit});
  3546. $urow = $sth->rows;
  3547. $dbh->disconnect();
  3548. }
  3549. # SQL-Laufzeit ermitteln
  3550. my $rt = tv_interval($st);
  3551. # Background-Laufzeit ermitteln
  3552. my $brt = tv_interval($bst);
  3553. $rt = $rt.",".$brt;
  3554. return "$name|$urow|$rt|0|$old|$new";
  3555. }
  3556. ####################################################################################################
  3557. # nichtblockierendes DB deviceRename / readingRename
  3558. ####################################################################################################
  3559. sub changeval_Push($) {
  3560. my ($string) = @_;
  3561. my ($name,$device,$reading,$runtime_string_first,$runtime_string_next,$ts) = split("\\§", $string);
  3562. my $hash = $defs{$name};
  3563. my $dbloghash = $hash->{dbloghash};
  3564. my $dbconn = $dbloghash->{dbconn};
  3565. my $dbuser = $dbloghash->{dbuser};
  3566. my $dblogname = $dbloghash->{NAME};
  3567. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3568. my $table = "history";
  3569. my $complex = $hash->{HELPER}{COMPLEX}; # einfache oder komplexe Werteersetzung
  3570. my ($dbh,$err,$sql,$urow);
  3571. # Background-Startzeit
  3572. my $bst = [gettimeofday];
  3573. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1 });};
  3574. if ($@) {
  3575. $err = encode_base64($@,"");
  3576. Log3 ($name, 2, "DbRep $name - $@");
  3577. return "$name|''|''|$err";
  3578. }
  3579. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  3580. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  3581. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  3582. # SQL-Startzeit
  3583. my $st = [gettimeofday];
  3584. my ($sth,$old,$new);
  3585. eval { $dbh->begin_work() if($dbh->{AutoCommit}); }; # Transaktion wenn gewünscht und autocommit ein
  3586. if ($@) {
  3587. Log3($name, 2, "DbRep $name -> Error start transaction - $@");
  3588. }
  3589. if (!$complex) {
  3590. $old = delete $hash->{HELPER}{OLDVAL};
  3591. $new = delete $hash->{HELPER}{NEWVAL};
  3592. # SQL zusammenstellen für DB-Operation
  3593. Log3 ($name, 5, "DbRep $name -> Change old value \"$old\" to new value \"$new\" in database $dblogname ");
  3594. # prepare DB operation
  3595. $old =~ s/'/''/g; # escape ' with ''
  3596. $new =~ s/'/''/g; # escape ' with ''
  3597. # SQL zusammenstellen für DB-Update
  3598. my $addon = $old =~ /%/?"WHERE VALUE LIKE '$old'":"WHERE VALUE='$old'";
  3599. if ($IsTimeSet) {
  3600. $sql = DbRep_createUpdateSql($hash,$table,"TIMESTAMP=TIMESTAMP,VALUE='$new' $addon",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  3601. } else {
  3602. $sql = DbRep_createUpdateSql($hash,$table,"TIMESTAMP=TIMESTAMP,VALUE='$new' $addon",$device,$reading,undef,undef,'');
  3603. }
  3604. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3605. $sth = $dbh->prepare($sql) ;
  3606. $old =~ s/''/'/g; # escape back
  3607. $new =~ s/''/'/g; # escape back
  3608. eval { $sth->execute(); };
  3609. if ($@) {
  3610. $err = encode_base64($@,"");
  3611. Log3 ($name, 2, "DbRep $name - Failed to change old value \"$old\" to new value \"$new\": $@");
  3612. $dbh->rollback() if(!$dbh->{AutoCommit});
  3613. $dbh->disconnect();
  3614. return "$name|''|''|$err";
  3615. } else {
  3616. $dbh->commit() if(!$dbh->{AutoCommit});
  3617. $urow = $sth->rows;
  3618. }
  3619. } else {
  3620. $old = delete $hash->{HELPER}{OLDVAL};
  3621. $new = delete $hash->{HELPER}{NEWVAL};
  3622. $old =~ s/'/''/g; # escape ' with ''
  3623. # Timestampstring to Array
  3624. my @ts = split("\\|", $ts);
  3625. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  3626. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  3627. $urow = 0;
  3628. my $selspec = "DEVICE,READING,TIMESTAMP,VALUE,UNIT";
  3629. my $addon = $old =~ /%/?"AND VALUE LIKE '$old'":"AND VALUE='$old'";
  3630. foreach my $row (@ts) {
  3631. my @a = split("#", $row);
  3632. my $runtime_string = $a[0];
  3633. my $runtime_string_first = $a[1];
  3634. my $runtime_string_next = $a[2];
  3635. if ($IsTimeSet || $IsAggrSet) {
  3636. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon);
  3637. } else {
  3638. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,$addon);
  3639. }
  3640. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3641. eval{ $sth = $dbh->prepare($sql);
  3642. $sth->execute();
  3643. };
  3644. if ($@) {
  3645. $err = encode_base64($@,"");
  3646. Log3 ($name, 2, "DbRep $name - $@");
  3647. $dbh->disconnect;
  3648. return "$name|''|''|$err";
  3649. }
  3650. no warnings 'uninitialized';
  3651. # DEVICE _ESC_ READING _ESC_ DATE _ESC_ TIME _ESC_ VALUE _ESC_ UNIT
  3652. my @row_array = map { $_->[0]."_ESC_".$_->[1]."_ESC_".($_->[2] =~ s/ /_ESC_/r)."_ESC_".$_->[3]."_ESC_".$_->[4]."\n" } @{$sth->fetchall_arrayref()};
  3653. use warnings;
  3654. Log3 ($name, 4, "DbRep $name - Now change values of selected array ... ");
  3655. foreach my $upd (@row_array) {
  3656. # für jeden selektierten (zu ändernden) Datensatz Userfunktion anwenden und updaten
  3657. my ($device,$reading,$date,$time,$value,$unit) = ($upd =~ /^(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)$/);
  3658. my $oval = $value; # Selektkriterium für Update alter Valuewert
  3659. my $VALUE = $value;
  3660. my $UNIT = $unit;
  3661. eval $new;
  3662. if ($@) {
  3663. $err = encode_base64($@,"");
  3664. Log3 ($name, 2, "DbRep $name - $@");
  3665. $dbh->disconnect;
  3666. return "$name|''|''|$err";
  3667. }
  3668. $value = $VALUE if(defined $VALUE);
  3669. $unit = $UNIT if(defined $UNIT);
  3670. # Daten auf maximale Länge beschneiden (DbLog-Funktion !)
  3671. (undef,undef,undef,undef,$value,$unit) = DbLog_cutCol($hash->{dbloghash},undef,undef,undef,undef,$value,$unit);
  3672. $value =~ s/'/''/g; # escape ' with ''
  3673. $unit =~ s/'/''/g; # escape ' with ''
  3674. # SQL zusammenstellen für DB-Update
  3675. $sql = "UPDATE history SET TIMESTAMP=TIMESTAMP,VALUE='$value',UNIT='$unit' WHERE TIMESTAMP = '$date $time' AND DEVICE = '$device' AND READING = '$reading' AND VALUE='$oval'";
  3676. Log3 ($name, 5, "DbRep $name - SQL execute: $sql");
  3677. $sth = $dbh->prepare($sql) ;
  3678. $value =~ s/''/'/g; # escape back
  3679. $unit =~ s/''/'/g; # escape back
  3680. eval { $sth->execute(); };
  3681. if ($@) {
  3682. $err = encode_base64($@,"");
  3683. Log3 ($name, 2, "DbRep $name - Failed to change old value \"$old\" to new value \"$new\": $@");
  3684. $dbh->rollback() if(!$dbh->{AutoCommit});
  3685. $dbh->disconnect();
  3686. return "$name|''|''|$err";
  3687. } else {
  3688. $dbh->commit() if(!$dbh->{AutoCommit});
  3689. $urow++;
  3690. }
  3691. }
  3692. }
  3693. }
  3694. $dbh->disconnect();
  3695. # SQL-Laufzeit ermitteln
  3696. my $rt = tv_interval($st);
  3697. # Background-Laufzeit ermitteln
  3698. my $brt = tv_interval($bst);
  3699. $rt = $rt.",".$brt;
  3700. return "$name|$urow|$rt|0|$old|$new";
  3701. }
  3702. ####################################################################################################
  3703. # Auswertungsroutine DB deviceRename/readingRename/changeValue
  3704. ####################################################################################################
  3705. sub change_Done($) {
  3706. my ($string) = @_;
  3707. my @a = split("\\|",$string);
  3708. my $hash = $defs{$a[0]};
  3709. my $name = $hash->{NAME};
  3710. my $urow = $a[1];
  3711. my $bt = $a[2];
  3712. my ($rt,$brt) = split(",", $bt);
  3713. my $err = $a[3]?decode_base64($a[3]):undef;
  3714. my $old = $a[4];
  3715. my $new = $a[5];
  3716. my $renmode = delete $hash->{HELPER}{RENMODE};
  3717. # Befehl nach Procedure ausführen
  3718. my $erread = DbRep_afterproc($hash, $renmode);
  3719. if ($err) {
  3720. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3721. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3722. delete($hash->{HELPER}{RUNNING_PID});
  3723. return;
  3724. }
  3725. # only for this block because of warnings if details of readings are not set
  3726. no warnings 'uninitialized';
  3727. readingsBeginUpdate($hash);
  3728. ReadingsBulkUpdateValue ($hash, "number_lines_updated", $urow);
  3729. if($renmode eq "devren") {
  3730. ReadingsBulkUpdateValue ($hash, "device_renamed", "old: ".$old." to new: ".$new) if($urow != 0);
  3731. ReadingsBulkUpdateValue ($hash, "device_not_renamed", "Warning - old: ".$old." not found, not renamed to new: ".$new)
  3732. if($urow == 0);
  3733. }
  3734. if($renmode eq "readren") {
  3735. ReadingsBulkUpdateValue ($hash, "reading_renamed", "old: ".$old." to new: ".$new) if($urow != 0);
  3736. ReadingsBulkUpdateValue ($hash, "reading_not_renamed", "Warning - old: ".$old." not found, not renamed to new: ".$new)
  3737. if ($urow == 0);
  3738. }
  3739. if($renmode eq "changeval") {
  3740. ReadingsBulkUpdateValue ($hash, "value_changed", "old: ".$old." to new: ".$new) if($urow != 0);
  3741. ReadingsBulkUpdateValue ($hash, "value_not_changed", "Warning - old: ".$old." not found, not changed to new: ".$new)
  3742. if ($urow == 0);
  3743. }
  3744. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  3745. readingsEndUpdate($hash, 1);
  3746. if ($urow != 0) {
  3747. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - DEVICE renamed in \"$hash->{DATABASE}\", old: \"$old\", new: \"$new\", number: $urow ") if($renmode eq "devren");
  3748. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - READING renamed in \"$hash->{DATABASE}\", old: \"$old\", new: \"$new\", number: $urow ") if($renmode eq "readren");
  3749. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - VALUE changed in \"$hash->{DATABASE}\", old: \"$old\", new: \"$new\", number: $urow ") if($renmode eq "changeval");
  3750. } else {
  3751. 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");
  3752. 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");
  3753. Log3 ($name, 3, "DbRep ".(($hash->{ROLE} eq "Agent")?"Agent ":"")."$name - WARNING - old value \"$old\" not found in database \"$hash->{DATABASE}\" ") if($renmode eq "changeval");
  3754. }
  3755. delete($hash->{HELPER}{RUNNING_PID});
  3756. return;
  3757. }
  3758. ####################################################################################################
  3759. # nichtblockierende DB-Abfrage fetchrows
  3760. ####################################################################################################
  3761. sub fetchrows_DoParse($) {
  3762. my ($string) = @_;
  3763. my ($name,$table,$device,$reading,$runtime_string_first,$runtime_string_next) = split("\\|", $string);
  3764. my $hash = $defs{$name};
  3765. my $dbloghash = $hash->{dbloghash};
  3766. my $dbconn = $dbloghash->{dbconn};
  3767. my $dbuser = $dbloghash->{dbuser};
  3768. my $dblogname = $dbloghash->{NAME};
  3769. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3770. my $limit = AttrVal($name, "limit", 1000);
  3771. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3772. my $fetchroute = AttrVal($name, "fetchRoute", "descent");
  3773. $fetchroute = ($fetchroute eq "descent")?"DESC":"ASC";
  3774. my ($err,$dbh,$sth,$sql,$rowlist,$nrows);
  3775. # Background-Startzeit
  3776. my $bst = [gettimeofday];
  3777. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3778. if ($@) {
  3779. $err = encode_base64($@,"");
  3780. Log3 ($name, 2, "DbRep $name - $@");
  3781. return "$name|''|''|$err|''";
  3782. }
  3783. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  3784. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  3785. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  3786. # SQL zusammenstellen für DB-Abfrage
  3787. if ($IsTimeSet) {
  3788. $sql = DbRep_createSelectSql($hash,$table,"DEVICE,READING,TIMESTAMP,VALUE,UNIT",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'","ORDER BY TIMESTAMP $fetchroute LIMIT ".($limit+1));
  3789. } else {
  3790. $sql = DbRep_createSelectSql($hash,$table,"DEVICE,READING,TIMESTAMP,VALUE,UNIT",$device,$reading,undef,undef,"ORDER BY TIMESTAMP $fetchroute LIMIT ".($limit+1));
  3791. }
  3792. $sth = $dbh->prepare($sql);
  3793. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  3794. # SQL-Startzeit
  3795. my $st = [gettimeofday];
  3796. eval{$sth->execute();};
  3797. if ($@) {
  3798. $err = encode_base64($@,"");
  3799. Log3 ($name, 2, "DbRep $name - $@");
  3800. $dbh->disconnect;
  3801. return "$name|''|''|$err|''";
  3802. }
  3803. no warnings 'uninitialized';
  3804. my @row_array = map { $_->[0]."_ESC_".$_->[1]."_ESC_".($_->[2] =~ s/ /_ESC_/r)."_ESC_".$_->[3]."_ESC_".$_->[4]."\n" } @{$sth->fetchall_arrayref()};
  3805. use warnings;
  3806. $nrows = $#row_array+1; # Anzahl der Ergebniselemente
  3807. pop @row_array if($nrows>$limit); # das zuviel selektierte Element wegpoppen wenn Limit überschritten
  3808. s/\|/_E#S#C_/g for @row_array; # escape Pipe "|"
  3809. if ($utf8) {
  3810. $rowlist = Encode::encode_utf8(join('|', @row_array));
  3811. } else {
  3812. $rowlist = join('|', @row_array);
  3813. }
  3814. Log3 ($name, 5, "DbRep $name -> row result list:\n$rowlist");
  3815. # SQL-Laufzeit ermitteln
  3816. my $rt = tv_interval($st);
  3817. $dbh->disconnect;
  3818. # Daten müssen als Einzeiler zurückgegeben werden
  3819. $rowlist = encode_base64($rowlist,"");
  3820. # Background-Laufzeit ermitteln
  3821. my $brt = tv_interval($bst);
  3822. $rt = $rt.",".$brt;
  3823. return "$name|$rowlist|$rt|0|$nrows";
  3824. }
  3825. ####################################################################################################
  3826. # Auswertungsroutine der nichtblockierenden DB-Abfrage fetchrows
  3827. ####################################################################################################
  3828. sub fetchrows_ParseDone($) {
  3829. my ($string) = @_;
  3830. my @a = split("\\|",$string);
  3831. my $hash = $defs{$a[0]};
  3832. my $rowlist = decode_base64($a[1]);
  3833. my $bt = $a[2];
  3834. my ($rt,$brt) = split(",", $bt);
  3835. my $err = $a[3]?decode_base64($a[3]):undef;
  3836. my $nrows = $a[4];
  3837. my $name = $hash->{NAME};
  3838. my $reading = AttrVal($name, "reading", undef);
  3839. my $limit = AttrVal($name, "limit", 1000);
  3840. my $color = "<html><span style='color: #".AttrVal($name, "fetchMarkDuplicates", "000000").";'>"; # Highlighting doppelter DB-Einträge
  3841. $color =~ s/#// if($color =~ /red|blue|brown|green|orange/);
  3842. my $ecolor = "</span></html>"; # Ende Highlighting
  3843. my @i;
  3844. my @row;
  3845. my $reading_runtime_string;
  3846. if ($err) {
  3847. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  3848. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  3849. delete($hash->{HELPER}{RUNNING_PID});
  3850. return;
  3851. }
  3852. my @row_array = split("\\|", $rowlist);
  3853. s/_E#S#C_/\|/g for @row_array; # escaped Pipe return to "|"
  3854. Log3 ($name, 5, "DbRep $name - row_array decoded: @row_array");
  3855. # Readingaufbereitung
  3856. readingsBeginUpdate($hash);
  3857. my $orow;
  3858. my $dz = 1; # Index des Vorkommens im Selektionsarray
  3859. foreach my $row (@row_array) {
  3860. if($orow) {
  3861. if($orow eq $row) {
  3862. $dz++;
  3863. } else {
  3864. $dz = 1;
  3865. }
  3866. }
  3867. $orow = $row;
  3868. if($dz > 1) {
  3869. # Hervorhebung von multiplen Datensätzen
  3870. # $dz = "<html><span style='color: ".$color.";'>".$dz."</span></html>";
  3871. }
  3872. my @a = split("_ESC_", $row, 6);
  3873. my $dev = $a[0];
  3874. my $rea = $a[1];
  3875. $a[3] =~ s/:/-/g; # substituieren unsupported characters ":" -> siehe fhem.pl
  3876. my $ts = $a[2]."_".$a[3];
  3877. my $val = $a[4];
  3878. my $unt = $a[5];
  3879. $val = $unt?$val." ".$unt:$val;
  3880. if ($reading && AttrVal($hash->{NAME}, "readingNameMap", "")) {
  3881. if($dz > 1 && AttrVal($name, "fetchMarkDuplicates", undef)) {
  3882. $reading_runtime_string = $ts."__".$color.$dz."__".AttrVal($hash->{NAME}, "readingNameMap", "").$ecolor;
  3883. } else {
  3884. $reading_runtime_string = $ts."__".$dz."__".AttrVal($hash->{NAME}, "readingNameMap", "");
  3885. }
  3886. } else {
  3887. if($dz > 1 && AttrVal($name, "fetchMarkDuplicates", undef)) {
  3888. $reading_runtime_string = $ts."__".$color.$dz."__".$dev."__".$rea.$ecolor;
  3889. } else {
  3890. $reading_runtime_string = $ts."__".$dz."__".$dev."__".$rea;
  3891. }
  3892. }
  3893. ReadingsBulkUpdateValue($hash, $reading_runtime_string, $val);
  3894. }
  3895. my $sfx = AttrVal("global", "language", "EN");
  3896. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  3897. ReadingsBulkUpdateValue($hash, "number_fetched_rows", ($nrows>$limit)?$nrows-1:$nrows);
  3898. ReadingsBulkUpdateTimeState($hash,$brt,$rt,($nrows-$limit>0)?
  3899. "<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");
  3900. readingsEndUpdate($hash, 1);
  3901. delete($hash->{HELPER}{RUNNING_PID});
  3902. return;
  3903. }
  3904. ####################################################################################################
  3905. # DB-Abfrage delSeqDoublets
  3906. ####################################################################################################
  3907. sub delseqdoubl_DoParse($) {
  3908. my ($string) = @_;
  3909. my ($name,$opt,$device,$reading,$ts) = split("\\§", $string);
  3910. my $hash = $defs{$name};
  3911. my $dbloghash = $hash->{dbloghash};
  3912. my $dbconn = $dbloghash->{dbconn};
  3913. my $dbuser = $dbloghash->{dbuser};
  3914. my $dblogname = $dbloghash->{NAME};
  3915. my $dbpassword = $attr{"sec$dblogname"}{secret};
  3916. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  3917. my $limit = AttrVal($name, "limit", 1000);
  3918. my $var = AttrVal($name, "seqDoubletsVariance", undef);
  3919. my $table = "history";
  3920. my ($err,$dbh,$sth,$sql,$rowlist,$nrows,$selspec,$st,$var1,$var2);
  3921. # Background-Startzeit
  3922. my $bst = [gettimeofday];
  3923. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  3924. if ($@) {
  3925. $err = encode_base64($@,"");
  3926. Log3 ($name, 2, "DbRep $name - $@");
  3927. return "$name|''|''|$err|''|$opt";
  3928. }
  3929. # Timestampstring to Array
  3930. my @ts = split("\\|", $ts);
  3931. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  3932. $selspec = "DEVICE,READING,TIMESTAMP,VALUE";
  3933. # SQL zusammenstellen für DB-Abfrage
  3934. $sql = DbRep_createSelectSql($hash,$table,$selspec,$device,$reading,"?","?","ORDER BY DEVICE,READING,TIMESTAMP ASC");
  3935. $sth = $dbh->prepare_cached($sql);
  3936. # DB-Abfrage zeilenweise für jeden Timearray-Eintrag
  3937. my @remain;
  3938. my @todel;
  3939. my $nremain = 0;
  3940. my $ntodel = 0;
  3941. my $ndel = 0;
  3942. my $rt = 0;
  3943. no warnings 'uninitialized';
  3944. foreach my $row (@ts) {
  3945. my @a = split("#", $row);
  3946. my $runtime_string = $a[0];
  3947. my $runtime_string_first = $a[1];
  3948. my $runtime_string_next = $a[2];
  3949. $runtime_string = encode_base64($runtime_string,"");
  3950. # SQL-Startzeit
  3951. $st = [gettimeofday];
  3952. # SQL zusammenstellen für Logausgabe
  3953. my $sql1 = DbRep_createSelectSql($hash,$table,$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",'');
  3954. Log3 ($name, 4, "DbRep $name - SQL execute: $sql1");
  3955. eval{$sth->execute($runtime_string_first, $runtime_string_next);};
  3956. if ($@) {
  3957. $err = encode_base64($@,"");
  3958. Log3 ($name, 2, "DbRep $name - $@");
  3959. $dbh->disconnect;
  3960. return "$name|''|''|$err|''|$opt";
  3961. }
  3962. # SQL-Laufzeit ermitteln
  3963. $rt = $rt+tv_interval($st);
  3964. # Beginn Löschlogik, Zusammenstellen der löschenden DS (warping)
  3965. # Array @sel -> die VERBLEIBENDEN Datensätze, @warp -> die zu löschenden Datensätze
  3966. my (@sel,@warp);
  3967. my ($or,$oor,$odev,$oread,$oval,$ooval,$ndev,$nread,$nval);
  3968. my $i = 0;
  3969. foreach my $nr (map { $_->[0]."_ESC_".$_->[1]."_ESC_".($_->[2] =~ s/ /_ESC_/r)."_ESC_".$_->[3] } @{$sth->fetchall_arrayref()}) {
  3970. ($ndev,$nread,undef,undef,$nval) = split("_ESC_", $nr); # Werte des aktuellen Elements
  3971. $or = pop @sel; # das letzte Element der Liste
  3972. ($odev,$oread,undef,undef,$oval) = split("_ESC_", $or); # Value des letzten Elements
  3973. if (looks_like_number($oval) && $var) { # Varianz +- falls $val numerischer Wert
  3974. $var1 = $oval + $var;
  3975. $var2 = $oval - $var;
  3976. } else {
  3977. undef $var1;
  3978. undef $var2;
  3979. }
  3980. $oor = pop @sel; # das vorletzte Element der Liste
  3981. $ooval = (split '_ESC_', $oor)[-1]; # Value des vorletzten Elements
  3982. if ($ndev.$nread ne $odev.$oread) {
  3983. $i = 0; # neues Device/Reading in einer Periode -> ooor soll erhalten bleiben
  3984. push (@sel,$oor) if($oor);
  3985. push (@sel,$or) if($or);
  3986. push (@sel,$nr);
  3987. } elsif ($i>=2 && ($ooval eq $oval && $oval eq $nval) || ($i>=2 && $var1 && $var2 && ($ooval <= $var1) && ($var2 <= $ooval) && ($nval <= $var1) && ($var2 <= $nval)) ) {
  3988. push (@sel,$oor);
  3989. push (@sel,$nr);
  3990. push (@warp,$or); # Array der zu löschenden Datensätze
  3991. if ($opt =~ /delete/ && $or) { # delete Datensätze
  3992. my ($dev,$read,$date,$time,$val) = split("_ESC_", $or);
  3993. my $dt = $date." ".$time;
  3994. chomp($val);
  3995. $dev =~ s/'/''/g; # escape ' with ''
  3996. $read =~ s/'/''/g; # escape ' with ''
  3997. $val =~ s/'/''/g; # escape ' with ''
  3998. $st = [gettimeofday];
  3999. my $dsql = "delete FROM $table where TIMESTAMP = '$dt' AND DEVICE = '$dev' AND READING = '$read' AND VALUE = '$val';";
  4000. my $sthd = $dbh->prepare($dsql);
  4001. Log3 ($name, 4, "DbRep $name - SQL execute: $dsql");
  4002. eval {$sthd->execute();};
  4003. if ($@) {
  4004. $err = encode_base64($@,"");
  4005. Log3 ($name, 2, "DbRep $name - $@");
  4006. $dbh->disconnect;
  4007. return "$name|''|''|$err|''|$opt";
  4008. }
  4009. $ndel = $ndel+$sthd->rows;
  4010. $dbh->commit() if(!$dbh->{AutoCommit});
  4011. $rt = $rt+tv_interval($st);
  4012. }
  4013. } else {
  4014. push (@sel,$oor) if($oor);
  4015. push (@sel,$or) if($or);
  4016. push (@sel,$nr);
  4017. }
  4018. $i++;
  4019. }
  4020. if(@sel && $opt =~ /adviceRemain/) {
  4021. # die verbleibenden Datensätze nach Ausführung (nur zur Anzeige)
  4022. push(@remain,@sel) if($#remain+1 < $limit);
  4023. }
  4024. if(@warp && $opt =~ /adviceDelete/) {
  4025. # die zu löschenden Datensätze (nur zur Anzeige)
  4026. push(@todel,@warp) if($#todel+1 < $limit);
  4027. }
  4028. $nremain = $nremain + $#sel+1 if(@sel);
  4029. $ntodel = $ntodel + $#warp+1 if(@warp);
  4030. my $sum = $nremain+$ntodel;
  4031. Log3 ($name, 3, "DbRep $name -> rows analyzed by \"$hash->{LASTCMD}\": $sum") if($sum && $opt =~ /advice/);
  4032. }
  4033. Log3 ($name, 3, "DbRep $name -> rows deleted by \"$hash->{LASTCMD}\": $ndel") if($ndel);
  4034. my $retn = ($opt =~ /adviceRemain/)?$nremain:($opt =~ /adviceDelete/)?$ntodel:$ndel;
  4035. my @retarray = ($opt =~ /adviceRemain/)?@remain:($opt =~ /adviceDelete/)?@todel:" ";
  4036. s/\|/_E#S#C_/g for @retarray; # escape Pipe "|"
  4037. if ($utf8 && @retarray) {
  4038. $rowlist = Encode::encode_utf8(join('|', @retarray));
  4039. } elsif(@retarray) {
  4040. $rowlist = join('|', @retarray);
  4041. } else {
  4042. $rowlist = 0;
  4043. }
  4044. use warnings;
  4045. Log3 ($name, 5, "DbRep $name -> row result list:\n$rowlist");
  4046. $dbh->disconnect;
  4047. # Daten müssen als Einzeiler zurückgegeben werden
  4048. $rowlist = encode_base64($rowlist,"");
  4049. # Background-Laufzeit ermitteln
  4050. my $brt = tv_interval($bst);
  4051. $rt = $rt.",".$brt;
  4052. return "$name|$rowlist|$rt|0|$retn|$opt";
  4053. }
  4054. ####################################################################################################
  4055. # Auswertungsroutine delSeqDoublets
  4056. ####################################################################################################
  4057. sub delseqdoubl_ParseDone($) {
  4058. my ($string) = @_;
  4059. my @a = split("\\|",$string);
  4060. my $hash = $defs{$a[0]};
  4061. my $rowlist = decode_base64($a[1]);
  4062. my $bt = $a[2];
  4063. my ($rt,$brt) = split(",", $bt);
  4064. my $err = $a[3]?decode_base64($a[3]):undef;
  4065. my $nrows = $a[4];
  4066. my $opt = $a[5];
  4067. my $name = $hash->{NAME};
  4068. my $reading = AttrVal($name, "reading", undef);
  4069. my $limit = AttrVal($name, "limit", 1000);
  4070. my @row;
  4071. my $l = 1;
  4072. my $reading_runtime_string;
  4073. my $erread;
  4074. if ($err) {
  4075. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4076. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4077. delete($hash->{HELPER}{RUNNING_PID});
  4078. return;
  4079. }
  4080. # Befehl nach Procedure ausführen
  4081. $erread = DbRep_afterproc($hash, "delSeq");
  4082. # Readingaufbereitung
  4083. readingsBeginUpdate($hash);
  4084. no warnings 'uninitialized';
  4085. if ($opt !~ /delete/ && $rowlist) {
  4086. my @row_array = split("\\|", $rowlist);
  4087. s/_E#S#C_/\|/g for @row_array; # escaped Pipe return to "|"
  4088. Log3 ($name, 5, "DbRep $name - row_array decoded: @row_array");
  4089. foreach my $row (@row_array) {
  4090. last if($l >= $limit);
  4091. my @a = split("_ESC_", $row, 5);
  4092. my $dev = $a[0];
  4093. my $rea = $a[1];
  4094. $a[3] =~ s/:/-/g; # substituieren unsupported characters ":" -> siehe fhem.pl
  4095. my $ts = $a[2]."_".$a[3];
  4096. my $val = $a[4];
  4097. if ($reading && AttrVal($hash->{NAME}, "readingNameMap", "")) {
  4098. $reading_runtime_string = $ts."__".AttrVal($hash->{NAME}, "readingNameMap", "") ;
  4099. } else {
  4100. $reading_runtime_string = $ts."__".$dev."__".$rea;
  4101. }
  4102. ReadingsBulkUpdateValue($hash, $reading_runtime_string, $val);
  4103. $l++;
  4104. }
  4105. }
  4106. use warnings;
  4107. my $sfx = AttrVal("global", "language", "EN");
  4108. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  4109. my $rnam = ($opt =~ /adviceRemain/)?"number_rows_to_remain":($opt =~ /adviceDelete/)?"number_rows_to_delete":"number_rows_deleted";
  4110. ReadingsBulkUpdateValue($hash, "$rnam", "$nrows");
  4111. ReadingsBulkUpdateTimeState($hash,$brt,$rt,($l >= $limit)?
  4112. "<html>done - Warning: not all items are shown, adjust attribute <a href='https://fhem.de/commandref${sfx}.html#DbRepattrlimit' target='_blank'>limit</a> if you want see more</html>":"done");
  4113. readingsEndUpdate($hash, 1);
  4114. delete($hash->{HELPER}{RUNNING_PID});
  4115. return;
  4116. }
  4117. ####################################################################################################
  4118. # nichtblockierende DB-Funktion expfile
  4119. ####################################################################################################
  4120. sub expfile_DoParse($) {
  4121. my ($string) = @_;
  4122. my ($name, $device, $reading, $ts) = split("\\§", $string);
  4123. my $hash = $defs{$name};
  4124. my $dbloghash = $hash->{dbloghash};
  4125. my $dbconn = $dbloghash->{dbconn};
  4126. my $dbuser = $dbloghash->{dbuser};
  4127. my $dblogname = $dbloghash->{NAME};
  4128. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4129. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  4130. my ($dbh,$sth,$sql);
  4131. my $err=0;
  4132. # Background-Startzeit
  4133. my $bst = [gettimeofday];
  4134. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  4135. if ($@) {
  4136. $err = encode_base64($@,"");
  4137. Log3 ($name, 2, "DbRep $name - $@");
  4138. return "$name|''|''|$err|''|''";
  4139. }
  4140. my $outfile = AttrVal($name, "expimpfile", undef);
  4141. if (open(FH, ">:utf8", "$outfile")) {
  4142. binmode (FH) if(!$utf8);
  4143. } else {
  4144. $err = encode_base64("could not open ".$outfile.": ".$!,"");
  4145. return "$name|''|''|$err";
  4146. }
  4147. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  4148. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  4149. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  4150. # Timestampstring to Array
  4151. my @ts = split("\\|", $ts);
  4152. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  4153. # SQL-Startzeit
  4154. my $st = [gettimeofday];
  4155. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  4156. my $arrstr;
  4157. my $nrows = 0;
  4158. my $addon = "ORDER BY TIMESTAMP";
  4159. no warnings 'uninitialized';
  4160. foreach my $row (@ts) {
  4161. my @a = split("#", $row);
  4162. my $runtime_string = $a[0];
  4163. my $runtime_string_first = $a[1];
  4164. my $runtime_string_next = $a[2];
  4165. if ($IsTimeSet || $IsAggrSet) {
  4166. $sql = DbRep_createSelectSql($hash,"history","TIMESTAMP,DEVICE,TYPE,EVENT,READING,VALUE,UNIT",$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon);
  4167. } else {
  4168. $sql = DbRep_createSelectSql($hash,"history","TIMESTAMP,DEVICE,TYPE,EVENT,READING,VALUE,UNIT",$device,$reading,undef,undef,$addon);
  4169. }
  4170. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  4171. eval{ $sth = $dbh->prepare($sql);
  4172. $sth->execute();
  4173. };
  4174. if ($@) {
  4175. $err = encode_base64($@,"");
  4176. Log3 ($name, 2, "DbRep $name - $@");
  4177. $dbh->disconnect;
  4178. return "$name|''|''|$err|''|''";
  4179. }
  4180. while (my $row = $sth->fetchrow_arrayref) {
  4181. print FH DbRep_charfilter(join(',', map { s{"}{""}g; "\"$_\"";} @$row)), "\n";
  4182. Log3 ($name, 5, "DbRep $name -> write row: @$row");
  4183. # Anzahl der Datensätze
  4184. $nrows++;
  4185. }
  4186. }
  4187. close(FH);
  4188. # SQL-Laufzeit ermitteln
  4189. my $rt = tv_interval($st);
  4190. $sth->finish;
  4191. $dbh->disconnect;
  4192. # Background-Laufzeit ermitteln
  4193. my $brt = tv_interval($bst);
  4194. $rt = $rt.",".$brt;
  4195. return "$name|$nrows|$rt|$err|$device|$reading";
  4196. }
  4197. ####################################################################################################
  4198. # Auswertungsroutine der nichtblockierenden DB-Funktion expfile
  4199. ####################################################################################################
  4200. sub expfile_ParseDone($) {
  4201. my ($string) = @_;
  4202. my @a = split("\\|",$string);
  4203. my $hash = $defs{$a[0]};
  4204. my $nrows = $a[1];
  4205. my $bt = $a[2];
  4206. my ($rt,$brt) = split(",", $bt);
  4207. my $err = $a[3]?decode_base64($a[3]):undef;
  4208. my $name = $hash->{NAME};
  4209. my $device = $a[4];
  4210. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  4211. my $reading = $a[5];
  4212. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  4213. if ($err) {
  4214. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4215. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4216. delete($hash->{HELPER}{RUNNING_PID});
  4217. return;
  4218. }
  4219. # only for this block because of warnings if details of readings are not set
  4220. no warnings 'uninitialized';
  4221. my $ds = $device." -- " if ($device);
  4222. my $rds = $reading." -- " if ($reading);
  4223. my $export_string = $ds.$rds." -- ROWS EXPORTED TO FILE -- ";
  4224. readingsBeginUpdate($hash);
  4225. ReadingsBulkUpdateValue ($hash, $export_string, $nrows);
  4226. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  4227. readingsEndUpdate($hash, 1);
  4228. my $rows = $ds.$rds.$nrows;
  4229. Log3 ($name, 3, "DbRep $name - Number of exported datasets from $hash->{DATABASE} to file ".AttrVal($name, "expimpfile", undef).": $rows.");
  4230. delete($hash->{HELPER}{RUNNING_PID});
  4231. return;
  4232. }
  4233. ####################################################################################################
  4234. # nichtblockierende DB-Funktion impfile
  4235. ####################################################################################################
  4236. sub impfile_Push($) {
  4237. my ($name) = @_;
  4238. my $hash = $defs{$name};
  4239. my $dbloghash = $hash->{dbloghash};
  4240. my $dbconn = $dbloghash->{dbconn};
  4241. my $dbuser = $dbloghash->{dbuser};
  4242. my $dblogname = $dbloghash->{NAME};
  4243. my $dbmodel = $hash->{dbloghash}{MODEL};
  4244. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4245. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  4246. my $err=0;
  4247. my $sth;
  4248. # Background-Startzeit
  4249. my $bst = [gettimeofday];
  4250. my $dbh;
  4251. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  4252. if ($@) {
  4253. $err = encode_base64($@,"");
  4254. Log3 ($name, 2, "DbRep $name - $@");
  4255. return "$name|''|''|$err";
  4256. }
  4257. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  4258. my ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh);
  4259. my $infile = AttrVal($name, "expimpfile", undef);
  4260. if (open(FH, "<:utf8", "$infile")) {
  4261. binmode (FH) if(!$utf8);
  4262. } else {
  4263. $err = encode_base64("could not open ".$infile.": ".$!,"");
  4264. return "$name|''|''|$err";
  4265. }
  4266. # only for this block because of warnings if details inline is not set
  4267. no warnings 'uninitialized';
  4268. # SQL-Startzeit
  4269. my $st = [gettimeofday];
  4270. my $al;
  4271. # Datei zeilenweise einlesen und verarbeiten !
  4272. # Beispiel Inline:
  4273. # "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573",""
  4274. # insert history mit/ohne primary key
  4275. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  4276. eval { $sth = $dbh->prepare_cached("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  4277. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  4278. eval { $sth = $dbh->prepare_cached("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  4279. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  4280. eval { $sth = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  4281. } else {
  4282. eval { $sth = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  4283. }
  4284. if ($@) {
  4285. $err = encode_base64($@,"");
  4286. Log3 ($name, 2, "DbRep $name - $@");
  4287. $dbh->disconnect();
  4288. return "$name|''|''|$err";
  4289. }
  4290. $dbh->begin_work();
  4291. my $irowdone = 0;
  4292. my $irowcount = 0;
  4293. my $warn = 0;
  4294. while (<FH>) {
  4295. $al = $_;
  4296. chomp $al;
  4297. my @alarr = split("\",\"", $al);
  4298. foreach(@alarr) {
  4299. tr/"//d;
  4300. }
  4301. my $i_timestamp = $alarr[0];
  4302. # $i_timestamp =~ tr/"//d;
  4303. my $i_device = $alarr[1];
  4304. my $i_type = $alarr[2];
  4305. my $i_event = $alarr[3];
  4306. my $i_reading = $alarr[4];
  4307. my $i_value = $alarr[5];
  4308. my $i_unit = $alarr[6] ? $alarr[6]: " ";
  4309. $irowcount++;
  4310. next if(!$i_timestamp); #leerer Datensatz
  4311. # check ob TIMESTAMP Format ok ?
  4312. my ($i_date, $i_time) = split(" ",$i_timestamp);
  4313. if ($i_date !~ /(\d{4})-(\d{2})-(\d{2})/ || $i_time !~ /(\d{2}):(\d{2}):(\d{2})/) {
  4314. $err = encode_base64("Format of date/time is not valid in row $irowcount of $infile. Must be format \"YYYY-MM-DD HH:MM:SS\" !","");
  4315. Log3 ($name, 2, "DbRep $name -> ERROR - Import from file $infile was not done. Invalid date/time field format in row $irowcount.");
  4316. close(FH);
  4317. $dbh->rollback;
  4318. return "$name|''|''|$err";
  4319. }
  4320. # Daten auf maximale Länge (entsprechend der Feldlänge in DbLog DB create-scripts) beschneiden wenn nicht SQLite
  4321. if ($dbmodel ne 'SQLITE') {
  4322. $i_device = substr($i_device,0, $dbrep_col{DEVICE});
  4323. $i_event = substr($i_event,0, $dbrep_col{EVENT});
  4324. $i_reading = substr($i_reading,0, $dbrep_col{READING});
  4325. $i_value = substr($i_value,0, $dbrep_col{VALUE});
  4326. $i_unit = substr($i_unit,0, $dbrep_col{UNIT}) if($i_unit);
  4327. }
  4328. 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");
  4329. if($i_timestamp && $i_device && $i_reading) {
  4330. eval {$sth->execute($i_timestamp, $i_device, $i_type, $i_event, $i_reading, $i_value, $i_unit);};
  4331. if ($@) {
  4332. $err = encode_base64($@,"");
  4333. Log3 ($name, 2, "DbRep $name - Failed to insert new dataset into database: $@");
  4334. close(FH);
  4335. $dbh->rollback;
  4336. $dbh->disconnect;
  4337. return "$name|''|''|$err";
  4338. } else {
  4339. $irowdone++
  4340. }
  4341. } else {
  4342. my $c = !$i_timestamp?"field \"timestamp\" is empty":!$i_device?"field \"device\" is empty":"field \"reading\" is empty";
  4343. $err = encode_base64("format error in in row $irowcount of $infile - cause: $c","");
  4344. Log3 ($name, 2, "DbRep $name -> ERROR - Import of datasets NOT done. Formaterror in row $irowcount of $infile - cause: $c");
  4345. close(FH);
  4346. $dbh->rollback;
  4347. $dbh->disconnect;
  4348. return "$name|''|''|$err";
  4349. }
  4350. }
  4351. $dbh->commit;
  4352. $dbh->disconnect;
  4353. close(FH);
  4354. # SQL-Laufzeit ermitteln
  4355. my $rt = tv_interval($st);
  4356. # Background-Laufzeit ermitteln
  4357. my $brt = tv_interval($bst);
  4358. $rt = $rt.",".$brt;
  4359. return "$name|$irowdone|$rt|$err";
  4360. }
  4361. ####################################################################################################
  4362. # Auswertungsroutine der nichtblockierenden DB-Funktion impfile
  4363. ####################################################################################################
  4364. sub impfile_PushDone($) {
  4365. my ($string) = @_;
  4366. my @a = split("\\|",$string);
  4367. my $hash = $defs{$a[0]};
  4368. my $irowdone = $a[1];
  4369. my $bt = $a[2];
  4370. my ($rt,$brt) = split(",", $bt);
  4371. my $err = $a[3]?decode_base64($a[3]):undef;
  4372. my $name = $hash->{NAME};
  4373. if ($err) {
  4374. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4375. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4376. delete($hash->{HELPER}{RUNNING_PID});
  4377. return;
  4378. }
  4379. # only for this block because of warnings if details of readings are not set
  4380. no warnings 'uninitialized';
  4381. my $import_string = " -- ROWS IMPORTED FROM FILE -- ";
  4382. readingsBeginUpdate($hash);
  4383. ReadingsBulkUpdateValue ($hash, $import_string, $irowdone);
  4384. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  4385. readingsEndUpdate($hash, 1);
  4386. Log3 ($name, 3, "DbRep $name - Number of imported datasets to $hash->{DATABASE} from file ".AttrVal($name, "expimpfile", undef).": $irowdone");
  4387. delete($hash->{HELPER}{RUNNING_PID});
  4388. return;
  4389. }
  4390. ####################################################################################################
  4391. # nichtblockierende DB-Abfrage sqlCmd - generischer SQL-Befehl - name | opt | sqlcommand
  4392. ####################################################################################################
  4393. # set logdbrep sqlCmd select count(*) from history
  4394. # set logdbrep sqlCmd select DEVICE,count(*) from history group by DEVICE HAVING count(*) > 10000
  4395. sub sqlCmd_DoParse($) {
  4396. my ($string) = @_;
  4397. my ($name, $opt, $runtime_string_first, $runtime_string_next, $cmd) = split("\\|", $string);
  4398. my $hash = $defs{$name};
  4399. my $dbloghash = $hash->{dbloghash};
  4400. my $dbconn = $dbloghash->{dbconn};
  4401. my $dbuser = $dbloghash->{dbuser};
  4402. my $dblogname = $dbloghash->{NAME};
  4403. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4404. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  4405. my $srs = AttrVal($name, "sqlResultFieldSep", "|");
  4406. my $err;
  4407. # Background-Startzeit
  4408. my $bst = [gettimeofday];
  4409. my $dbh;
  4410. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  4411. if ($@) {
  4412. $err = encode_base64($@,"");
  4413. Log3 ($name, 2, "DbRep $name - $@");
  4414. return "$name|''|$opt|$cmd|''|''|$err";
  4415. }
  4416. # only for this block because of warnings if details of readings are not set
  4417. no warnings 'uninitialized';
  4418. my $sql = ($cmd =~ m/\;$/)?$cmd:$cmd.";";
  4419. # Allow inplace replacement of keywords for timings (use time attribute syntax)
  4420. $sql =~ s/§timestamp_begin§/'$runtime_string_first'/g;
  4421. $sql =~ s/§timestamp_end§/'$runtime_string_next'/g;
  4422. # Debug "SQL :".$sql.":";
  4423. Log3($name, 4, "DbRep $name - SQL execute: $sql");
  4424. # SQL-Startzeit
  4425. my $st = [gettimeofday];
  4426. my ($sth,$r);
  4427. eval {$sth = $dbh->prepare($sql);
  4428. $r = $sth->execute();
  4429. };
  4430. if ($@) {
  4431. # error bei sql-execute
  4432. $err = encode_base64($@,"");
  4433. Log3 ($name, 2, "DbRep $name - ERROR - $@");
  4434. $dbh->disconnect;
  4435. return "$name|''|$opt|$sql|''|''|$err";
  4436. }
  4437. my @rows;
  4438. my $nrows = 0;
  4439. if($sql =~ m/^\s*(select|pragma|show)/is) {
  4440. while (my @line = $sth->fetchrow_array()) {
  4441. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  4442. my $row = join("$srs", @line);
  4443. # join Delimiter "§" escapen
  4444. $row =~ s/§/|°escaped°|/g;
  4445. push(@rows, $row);
  4446. # Anzahl der Datensätze
  4447. $nrows++;
  4448. }
  4449. } else {
  4450. $nrows = $sth->rows;
  4451. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  4452. if ($@) {
  4453. $err = encode_base64($@,"");
  4454. Log3 ($name, 2, "DbRep $name - ERROR - $@");
  4455. $dbh->disconnect;
  4456. return "$name|''|$opt|$sql|''|''|$err";
  4457. }
  4458. push(@rows, $r);
  4459. my $com = (split(" ",$sql, 2))[0];
  4460. Log3 ($name, 3, "DbRep $name - Number of entries processed in db $hash->{DATABASE}: $nrows by $com");
  4461. }
  4462. $sth->finish;
  4463. # SQL-Laufzeit ermitteln
  4464. my $rt = tv_interval($st);
  4465. $dbh->disconnect;
  4466. # Daten müssen als Einzeiler zurückgegeben werden
  4467. my $rowstring = join("§", @rows);
  4468. $rowstring = encode_base64($rowstring,"");
  4469. # Background-Laufzeit ermitteln
  4470. my $brt = tv_interval($bst);
  4471. $rt = $rt.",".$brt;
  4472. return "$name|$rowstring|$opt|$sql|$nrows|$rt|$err";
  4473. }
  4474. ####################################################################################################
  4475. # Auswertungsroutine der nichtblockierenden DB-Abfrage sqlCmd
  4476. ####################################################################################################
  4477. sub sqlCmd_ParseDone($) {
  4478. my ($string) = @_;
  4479. my @a = split("\\|",$string);
  4480. my $hash = $defs{$a[0]};
  4481. my $name = $hash->{NAME};
  4482. my $rowstring = decode_base64($a[1]);
  4483. my $opt = $a[2];
  4484. my $cmd = $a[3];
  4485. my $nrows = $a[4];
  4486. my $bt = $a[5];
  4487. my ($rt,$brt) = split(",", $bt);
  4488. my $err = $a[6]?decode_base64($a[6]):undef;
  4489. my $srf = AttrVal($name, "sqlResultFormat", "separated");
  4490. my $srs = AttrVal($name, "sqlResultFieldSep", "|");
  4491. if ($err) {
  4492. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4493. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4494. delete($hash->{HELPER}{RUNNING_PID});
  4495. return;
  4496. }
  4497. Log3 ($name, 5, "DbRep $name - SQL result decoded: $rowstring") if($rowstring);
  4498. no warnings 'uninitialized';
  4499. # Readingaufbereitung
  4500. readingsBeginUpdate($hash);
  4501. ReadingsBulkUpdateValue ($hash, "sqlCmd", $cmd);
  4502. ReadingsBulkUpdateValue ($hash, "sqlResultNumRows", $nrows);
  4503. # Drop-Down Liste bisherige sqlCmd-Befehle füllen und in Key-File sichern
  4504. # my $hl = $hash->{HELPER}{SQLHIST};
  4505. my @sqlhist = split(",",$hash->{HELPER}{SQLHIST});
  4506. $cmd =~ s/\s/&nbsp;/g;
  4507. $cmd =~ s/,/<c>/g;
  4508. my $hlc = AttrVal($name, "sqlCmdHistoryLength", 0); # Anzahl der Einträge in Drop-Down Liste
  4509. if(!@sqlhist || (@sqlhist && !($cmd ~~ @sqlhist))) {
  4510. unshift @sqlhist,$cmd;
  4511. pop @sqlhist if(@sqlhist > $hlc);
  4512. my $hl = join(",",@sqlhist);
  4513. $hash->{HELPER}{SQLHIST} = $hl;
  4514. DbRep_setCmdFile($name."_sqlCmdList",$hl,$hash);
  4515. }
  4516. if ($srf eq "sline") {
  4517. $rowstring =~ s/§/]|[/g;
  4518. $rowstring =~ s/\|°escaped°\|/§/g;
  4519. ReadingsBulkUpdateValue ($hash, "SqlResult", $rowstring);
  4520. } elsif ($srf eq "table") {
  4521. my $res = "<html><table border=2 bordercolor='darkgreen' cellspacing=0>";
  4522. my @rows = split( /§/, $rowstring );
  4523. my $row;
  4524. foreach $row ( @rows ) {
  4525. $row =~ s/\|°escaped°\|/§/g;
  4526. $row =~ s/$srs/\|/g if($srs !~ /\|/);
  4527. $row =~ s/\|/<\/td><td style='padding-right:5px;padding-left:5px'>/g;
  4528. $res .= "<tr><td style='padding-right:5px;padding-left:5px'>".$row."</td></tr>";
  4529. }
  4530. $row .= $res."</table></html>";
  4531. ReadingsBulkUpdateValue ($hash,"SqlResult", $row);
  4532. } elsif ($srf eq "mline") {
  4533. my $res = "<html>";
  4534. my @rows = split( /§/, $rowstring );
  4535. my $row;
  4536. foreach $row ( @rows ) {
  4537. $row =~ s/\|°escaped°\|/§/g;
  4538. $res .= $row."<br>";
  4539. }
  4540. $row .= $res."</html>";
  4541. ReadingsBulkUpdateValue ($hash, "SqlResult", $row );
  4542. } elsif ($srf eq "separated") {
  4543. my @rows = split( /§/, $rowstring );
  4544. my $bigint = @rows;
  4545. my $numd = ceil(log10($bigint));
  4546. my $formatstr = sprintf('%%%d.%dd', $numd, $numd);
  4547. my $i = 0;
  4548. foreach my $row ( @rows ) {
  4549. $i++;
  4550. $row =~ s/\|°escaped°\|/§/g;
  4551. my $fi = sprintf($formatstr, $i);
  4552. ReadingsBulkUpdateValue ($hash, "SqlResultRow_".$fi, $row);
  4553. }
  4554. } elsif ($srf eq "json") {
  4555. my %result = ();
  4556. my @rows = split( /§/, $rowstring );
  4557. my $bigint = @rows;
  4558. my $numd = ceil(log10($bigint));
  4559. my $formatstr = sprintf('%%%d.%dd', $numd, $numd);
  4560. my $i = 0;
  4561. foreach my $row ( @rows ) {
  4562. $i++;
  4563. $row =~ s/\|°escaped°\|/§/g;
  4564. my $fi = sprintf($formatstr, $i);
  4565. $result{$fi} = $row;
  4566. }
  4567. my $json = toJSON(\%result); # at least fhem.pl 14348 2017-05-22 20:25:06Z
  4568. ReadingsBulkUpdateValue ($hash, "SqlResult", $json);
  4569. }
  4570. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  4571. readingsEndUpdate($hash, 1);
  4572. delete($hash->{HELPER}{RUNNING_PID});
  4573. return;
  4574. }
  4575. ####################################################################################################
  4576. # nichtblockierende DB-Abfrage get db Metadaten
  4577. ####################################################################################################
  4578. sub dbmeta_DoParse($) {
  4579. my ($string) = @_;
  4580. my @a = split("\\|",$string);
  4581. my $name = $a[0];
  4582. my $hash = $defs{$name};
  4583. my $opt = $a[1];
  4584. my $dbloghash = $hash->{dbloghash};
  4585. my $dbconn = $dbloghash->{dbconn};
  4586. my $db = $hash->{DATABASE};
  4587. my $dbuser = $dbloghash->{dbuser};
  4588. my $dblogname = $dbloghash->{NAME};
  4589. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4590. my $dbmodel = $dbloghash->{MODEL};
  4591. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  4592. my ($dbh,$sth,$sql);
  4593. my $err;
  4594. # Background-Startzeit
  4595. my $bst = [gettimeofday];
  4596. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1, mysql_enable_utf8 => $utf8 });};
  4597. if ($@) {
  4598. $err = encode_base64($@,"");
  4599. Log3 ($name, 2, "DbRep $name - $@");
  4600. return "$name|''|''|''|$err";
  4601. }
  4602. # only for this block because of warnings if details of readings are not set
  4603. no warnings 'uninitialized';
  4604. # Liste der anzuzeigenden Parameter erzeugen, sonst alle ("%"), abhängig von $opt
  4605. my $param = AttrVal($name, "showVariables", "%") if($opt eq "dbvars");
  4606. $param = AttrVal($name, "showSvrInfo", "[A-Z_]") if($opt eq "svrinfo");
  4607. $param = AttrVal($name, "showStatus", "%") if($opt eq "dbstatus");
  4608. $param = "1" if($opt =~ /tableinfo|procinfo/); # Dummy-Eintrag für einen Schleifendurchlauf
  4609. my @parlist = split(",",$param);
  4610. # SQL-Startzeit
  4611. my $st = [gettimeofday];
  4612. my @row_array;
  4613. # due to incompatible changes made in MyQL 5.7.5, see http://johnemb.blogspot.de/2014/09/adding-or-removing-individual-sql-modes.html
  4614. if($dbmodel eq "MYSQL") {
  4615. eval {$dbh->do("SET sql_mode=(SELECT REPLACE(\@\@sql_mode,'ONLY_FULL_GROUP_BY',''));");};
  4616. }
  4617. if ($@) {
  4618. $err = encode_base64($@,"");
  4619. Log3 ($name, 2, "DbRep $name - $@");
  4620. $dbh->disconnect;
  4621. return "$name|''|''|''|$err";
  4622. }
  4623. if ($opt ne "svrinfo") {
  4624. foreach my $ple (@parlist) {
  4625. if ($opt eq "dbvars") {
  4626. $sql = "show variables like '$ple';";
  4627. } elsif ($opt eq "dbstatus") {
  4628. $sql = "show global status like '$ple';";
  4629. } elsif ($opt eq "tableinfo") {
  4630. $sql = "show Table Status from $db;";
  4631. } elsif ($opt eq "procinfo") {
  4632. $sql = "show full processlist;";
  4633. }
  4634. Log3($name, 4, "DbRep $name - SQL execute: $sql");
  4635. $sth = $dbh->prepare($sql);
  4636. eval {$sth->execute();};
  4637. if ($@) {
  4638. # error bei sql-execute
  4639. $err = encode_base64($@,"");
  4640. Log3 ($name, 2, "DbRep $name - $@");
  4641. $dbh->disconnect;
  4642. return "$name|''|''|''|$err";
  4643. } else {
  4644. # kein error bei sql-execute
  4645. if ($opt eq "tableinfo") {
  4646. $param = AttrVal($name, "showTableInfo", "[A-Z_]");
  4647. $param =~ s/,/\|/g;
  4648. $param =~ tr/%//d;
  4649. while ( my $line = $sth->fetchrow_hashref()) {
  4650. 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}");
  4651. if($line->{Name} =~ m/($param)/i) {
  4652. push(@row_array, $line->{Name}.".engine ".$line->{Engine}) if($line->{Engine});
  4653. push(@row_array, $line->{Name}.".version ".$line->{Version}) if($line->{Version});
  4654. push(@row_array, $line->{Name}.".row_format ".$line->{Row_format}) if($line->{Row_format});
  4655. push(@row_array, $line->{Name}.".number_of_rows ".$line->{Rows}) if($line->{Rows});
  4656. push(@row_array, $line->{Name}.".avg_row_length ".$line->{Avg_row_length}) if($line->{Avg_row_length});
  4657. push(@row_array, $line->{Name}.".data_length_MB ".sprintf("%.2f",$line->{Data_length}/1024/1024)) if($line->{Data_length});
  4658. push(@row_array, $line->{Name}.".max_data_length_MB ".sprintf("%.2f",$line->{Max_data_length}/1024/1024)) if($line->{Max_data_length});
  4659. push(@row_array, $line->{Name}.".index_length_MB ".sprintf("%.2f",$line->{Index_length}/1024/1024)) if($line->{Index_length});
  4660. push(@row_array, $line->{Name}.".data_index_length_MB ".sprintf("%.2f",($line->{Data_length}+$line->{Index_length})/1024/1024));
  4661. push(@row_array, $line->{Name}.".data_free_MB ".sprintf("%.2f",$line->{Data_free}/1024/1024)) if($line->{Data_free});
  4662. push(@row_array, $line->{Name}.".auto_increment ".$line->{Auto_increment}) if($line->{Auto_increment});
  4663. push(@row_array, $line->{Name}.".create_time ".$line->{Create_time}) if($line->{Create_time});
  4664. push(@row_array, $line->{Name}.".update_time ".$line->{Update_time}) if($line->{Update_time});
  4665. push(@row_array, $line->{Name}.".check_time ".$line->{Check_time}) if($line->{Check_time});
  4666. push(@row_array, $line->{Name}.".collation ".$line->{Collation}) if($line->{Collation});
  4667. push(@row_array, $line->{Name}.".checksum ".$line->{Checksum}) if($line->{Checksum});
  4668. push(@row_array, $line->{Name}.".create_options ".$line->{Create_options}) if($line->{Create_options});
  4669. push(@row_array, $line->{Name}.".comment ".$line->{Comment}) if($line->{Comment});
  4670. }
  4671. }
  4672. } elsif ($opt eq "procinfo") {
  4673. my $res = "<html><table border=2 bordercolor='darkgreen' cellspacing=0>";
  4674. $res .= "<tr><td style='padding-right:5px;padding-left:5px;font-weight:bold'>ID</td>";
  4675. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>USER</td>";
  4676. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>HOST</td>";
  4677. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>DB</td>";
  4678. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>CMD</td>";
  4679. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>TIME_Sec</td>";
  4680. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>STATE</td>";
  4681. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>INFO</td>";
  4682. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>PROGRESS</td></tr>";
  4683. while (my @line = $sth->fetchrow_array()) {
  4684. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  4685. my $row = join("|", @line);
  4686. $row =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\]^_`{|}~//cd;
  4687. $row =~ s/\|/<\/td><td style='padding-right:5px;padding-left:5px'>/g;
  4688. $res .= "<tr><td style='padding-right:5px;padding-left:5px'>".$row."</td></tr>";
  4689. }
  4690. my $tab .= $res."</table></html>";
  4691. push(@row_array, "ProcessList ".$tab);
  4692. } else {
  4693. while (my @line = $sth->fetchrow_array()) {
  4694. Log3 ($name, 4, "DbRep $name - SQL result: @line");
  4695. my $row = join("§", @line);
  4696. $row =~ s/ /_/g;
  4697. @line = split("§", $row);
  4698. push(@row_array, $line[0]." ".$line[1]);
  4699. }
  4700. }
  4701. }
  4702. $sth->finish;
  4703. }
  4704. } else {
  4705. $param =~ s/,/\|/g;
  4706. $param =~ tr/%//d;
  4707. # Log3 ($name, 5, "DbRep $name - showDbInfo: $param");
  4708. if($dbmodel eq 'SQLITE') {
  4709. my $sf = $dbh->sqlite_db_filename();
  4710. if ($@) {
  4711. # error bei sql-execute
  4712. $err = encode_base64($@,"");
  4713. Log3 ($name, 2, "DbRep $name - $@");
  4714. $dbh->disconnect;
  4715. return "$name|''|''|''|$err";
  4716. } else {
  4717. # kein error bei sql-execute
  4718. my $key = "SQLITE_DB_FILENAME";
  4719. push(@row_array, $key." ".$sf) if($key =~ m/($param)/i);
  4720. }
  4721. my @a = split(' ',qx(du -m $hash->{DATABASE})) if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  4722. my $key = "SQLITE_FILE_SIZE_MB";
  4723. push(@row_array, $key." ".$a[0]) if($key =~ m/($param)/i);
  4724. }
  4725. my $info;
  4726. while( my ($key,$value) = each(%GetInfoType) ) {
  4727. eval { $info = $dbh->get_info($GetInfoType{"$key"}) };
  4728. if ($@) {
  4729. $err = encode_base64($@,"");
  4730. Log3 ($name, 2, "DbRep $name - $@");
  4731. $dbh->disconnect;
  4732. return "$name|''|''|''|$err";
  4733. } else {
  4734. if($utf8) {
  4735. $info = Encode::encode_utf8($info) if($info);
  4736. }
  4737. push(@row_array, $key." ".$info) if($key =~ m/($param)/i);
  4738. }
  4739. }
  4740. }
  4741. # SQL-Laufzeit ermitteln
  4742. my $rt = tv_interval($st);
  4743. $dbh->disconnect;
  4744. my $rowlist = join('§', @row_array);
  4745. Log3 ($name, 5, "DbRep $name -> row_array: \n@row_array");
  4746. # Daten müssen als Einzeiler zurückgegeben werden
  4747. $rowlist = encode_base64($rowlist,"");
  4748. # Background-Laufzeit ermitteln
  4749. my $brt = tv_interval($bst);
  4750. $rt = $rt.",".$brt;
  4751. return "$name|$rowlist|$rt|$opt|0";
  4752. }
  4753. ####################################################################################################
  4754. # Auswertungsroutine der nichtblockierenden DB-Abfrage get db Metadaten
  4755. ####################################################################################################
  4756. sub dbmeta_ParseDone($) {
  4757. my ($string) = @_;
  4758. my @a = split("\\|",$string);
  4759. my $hash = $defs{$a[0]};
  4760. my $name = $hash->{NAME};
  4761. my $rowlist = decode_base64($a[1]);
  4762. my $bt = $a[2];
  4763. my $opt = $a[3];
  4764. my ($rt,$brt) = split(",", $bt);
  4765. my $err = $a[4]?decode_base64($a[4]):undef;
  4766. if ($err) {
  4767. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4768. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4769. delete($hash->{HELPER}{RUNNING_PID});
  4770. return;
  4771. }
  4772. # only for this block because of warnings if details of readings are not set
  4773. no warnings 'uninitialized';
  4774. # Readingaufbereitung
  4775. readingsBeginUpdate($hash);
  4776. my @row_array = split("§", $rowlist);
  4777. Log3 ($name, 5, "DbRep $name - SQL result decoded: \n@row_array") if(@row_array);
  4778. my $pre = "";
  4779. $pre = "VAR_" if($opt eq "dbvars");
  4780. $pre = "STAT_" if($opt eq "dbstatus");
  4781. $pre = "INFO_" if($opt eq "tableinfo");
  4782. foreach my $row (@row_array) {
  4783. my @a = split(" ", $row, 2);
  4784. my $k = $a[0];
  4785. my $v = $a[1];
  4786. ReadingsBulkUpdateValue ($hash, $pre.$k, $v);
  4787. }
  4788. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  4789. readingsEndUpdate($hash, 1);
  4790. # InternalTimer(time+0.5, "browser_refresh", $hash, 0);
  4791. delete($hash->{HELPER}{RUNNING_PID});
  4792. return;
  4793. }
  4794. ####################################################################################################
  4795. # optimize Tables alle Datenbanken
  4796. ####################################################################################################
  4797. sub DbRep_optimizeTables($) {
  4798. my ($name) = @_;
  4799. my $hash = $defs{$name};
  4800. my $dbloghash = $hash->{dbloghash};
  4801. my $dbconn = $dbloghash->{dbconn};
  4802. my $dbuser = $dbloghash->{dbuser};
  4803. my $dblogname = $dbloghash->{NAME};
  4804. my $dbmodel = $dbloghash->{MODEL};
  4805. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4806. my $dbname = $hash->{DATABASE};
  4807. my $value = 0;
  4808. my ($dbh,$sth,$query,$err,$r,$db_MB_start,$db_MB_end);
  4809. my (%db_tables,@tablenames);
  4810. # Background-Startzeit
  4811. my $bst = [gettimeofday];
  4812. # Verbindung mit DB
  4813. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  4814. if ($@) {
  4815. $err = encode_base64($@,"");
  4816. Log3 ($name, 2, "DbRep $name - $@");
  4817. return "$name|''|$err|''|''";
  4818. }
  4819. # SQL-Startzeit
  4820. my $st = [gettimeofday];
  4821. if ($dbmodel =~ /MYSQL/) {
  4822. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  4823. $query = "SHOW TABLE STATUS FROM `$dbname`";
  4824. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4825. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  4826. eval { $sth = $dbh->prepare($query);
  4827. $sth->execute;
  4828. };
  4829. if ($@) {
  4830. $err = encode_base64($@,"");
  4831. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  4832. $sth->finish;
  4833. $dbh->disconnect;
  4834. return "$name|''|$err|''|''";
  4835. }
  4836. while ( $value = $sth->fetchrow_hashref()) {
  4837. # verbose 5 logging
  4838. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  4839. foreach my $tk (sort(keys(%$value))) {
  4840. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  4841. }
  4842. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  4843. # check for old MySQL3-Syntax Type=xxx
  4844. if (defined $value->{Type}) {
  4845. # port old index type to index engine, so we can use the index Engine in the rest of the script
  4846. $value->{Engine} = $value->{Type};
  4847. }
  4848. $db_tables{$value->{Name}} = $value;
  4849. }
  4850. @tablenames = sort(keys(%db_tables));
  4851. if (@tablenames < 1) {
  4852. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  4853. Log3 ($name, 2, "DbRep $name - $err");
  4854. $err = encode_base64($@,"");
  4855. $sth->finish;
  4856. $dbh->disconnect;
  4857. return "$name|''|$err|''|''";
  4858. }
  4859. # Tabellen optimieren
  4860. $hash->{HELPER}{DBTABLES} = \%db_tables;
  4861. ($err,$db_MB_start,$db_MB_end) = DbRep_mysqlOptimizeTables($hash,$dbh,@tablenames);
  4862. if ($err) {
  4863. $err = encode_base64($err,"");
  4864. return "$name|''|$err|''|''";
  4865. }
  4866. }
  4867. if ($dbmodel =~ /SQLITE/) {
  4868. # Anfangsgröße ermitteln
  4869. $db_MB_start = (split(' ',qx(du -m $hash->{DATABASE})))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  4870. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  4871. $query ="VACUUM";
  4872. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4873. Log3 ($name, 3, "DbRep $name - VACUUM database $dbname....");
  4874. eval {$sth = $dbh->prepare($query);
  4875. $r = $sth->execute();
  4876. };
  4877. if ($@) {
  4878. $err = encode_base64($@,"");
  4879. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! SQLite-Error: ".$@);
  4880. $sth->finish;
  4881. $dbh->disconnect;
  4882. return "$name|''|$err|''|''";
  4883. }
  4884. # Endgröße ermitteln
  4885. $db_MB_end = (split(' ',qx(du -m $hash->{DATABASE})))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  4886. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  4887. }
  4888. if ($dbmodel =~ /POSTGRESQL/) {
  4889. # Anfangsgröße ermitteln
  4890. $query = "SELECT pg_size_pretty(pg_database_size('$dbname'))";
  4891. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4892. eval { $sth = $dbh->prepare($query);
  4893. $sth->execute;
  4894. };
  4895. if ($@) {
  4896. $err = encode_base64($@,"");
  4897. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  4898. $sth->finish;
  4899. $dbh->disconnect;
  4900. return "$name|''|$err|''|''";
  4901. }
  4902. $value = $sth->fetchrow();
  4903. $value =~ tr/MB//d;
  4904. $db_MB_start = sprintf("%.2f",$value);
  4905. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  4906. Log3 ($name, 3, "DbRep $name - VACUUM database $dbname....");
  4907. $query = "vacuum history";
  4908. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4909. eval {$sth = $dbh->prepare($query);
  4910. $sth->execute();
  4911. };
  4912. if ($@) {
  4913. $err = encode_base64($@,"");
  4914. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  4915. $sth->finish;
  4916. $dbh->disconnect;
  4917. return "$name|''|$err|''|''";
  4918. }
  4919. # Endgröße ermitteln
  4920. $query = "SELECT pg_size_pretty(pg_database_size('$dbname'))";
  4921. Log3 ($name, 5, "DbRep $name - current query: $query ");
  4922. eval { $sth = $dbh->prepare($query);
  4923. $sth->execute;
  4924. };
  4925. if ($@) {
  4926. $err = encode_base64($@,"");
  4927. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! PostgreSQL-Error: ".$@);
  4928. $sth->finish;
  4929. $dbh->disconnect;
  4930. return "$name|''|$err|''|''";
  4931. }
  4932. $value = $sth->fetchrow();
  4933. $value =~ tr/MB//d;
  4934. $db_MB_end = sprintf("%.2f",$value);
  4935. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  4936. }
  4937. $sth->finish;
  4938. $dbh->disconnect;
  4939. # SQL-Laufzeit ermitteln
  4940. my $rt = tv_interval($st);
  4941. # Background-Laufzeit ermitteln
  4942. my $brt = tv_interval($bst);
  4943. $rt = $rt.",".$brt;
  4944. Log3 ($name, 3, "DbRep $name - Optimize tables of database $dbname finished, total time used: ".sprintf("%.0f",$brt)." sec.");
  4945. return "$name|$rt|''|$db_MB_start|$db_MB_end";
  4946. }
  4947. ####################################################################################################
  4948. # Auswertungsroutine optimize tables
  4949. ####################################################################################################
  4950. sub DbRep_OptimizeDone($) {
  4951. my ($string) = @_;
  4952. my @a = split("\\|",$string);
  4953. my $hash = $defs{$a[0]};
  4954. my $bt = $a[1];
  4955. my ($rt,$brt) = split(",", $bt);
  4956. my $err = $a[2]?decode_base64($a[2]):undef;
  4957. my $db_MB_start = $a[3];
  4958. my $db_MB_end = $a[4];
  4959. my $name = $hash->{NAME};
  4960. my $erread;
  4961. delete($hash->{HELPER}{RUNNING_OPTIMIZE});
  4962. if ($err) {
  4963. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  4964. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  4965. return;
  4966. }
  4967. # only for this block because of warnings if details of readings are not set
  4968. no warnings 'uninitialized';
  4969. readingsBeginUpdate($hash);
  4970. ReadingsBulkUpdateValue($hash, "SizeDbBegin_MB", $db_MB_start);
  4971. ReadingsBulkUpdateValue($hash, "SizeDbEnd_MB", $db_MB_end);
  4972. readingsEndUpdate($hash, 1);
  4973. # Befehl nach Procedure ausführen
  4974. $erread = DbRep_afterproc($hash, "optimize");
  4975. my $state = $erread?$erread:"optimize tables finished";
  4976. readingsBeginUpdate($hash);
  4977. ReadingsBulkUpdateTimeState($hash,$brt,undef,$state);
  4978. readingsEndUpdate($hash, 1);
  4979. Log3 ($name, 3, "DbRep $name - Optimize tables finished successfully. ");
  4980. return;
  4981. }
  4982. ####################################################################################################
  4983. # nicht blockierende Dump-Routine für MySQL (clientSide)
  4984. ####################################################################################################
  4985. sub mysql_DoDumpClientSide($) {
  4986. my ($name) = @_;
  4987. my $hash = $defs{$name};
  4988. my $dbloghash = $hash->{dbloghash};
  4989. my $dbconn = $dbloghash->{dbconn};
  4990. my $dbuser = $dbloghash->{dbuser};
  4991. my $dblogname = $dbloghash->{NAME};
  4992. my $dbpassword = $attr{"sec$dblogname"}{secret};
  4993. my $dbname = $hash->{DATABASE};
  4994. my $dump_path_def = $attr{global}{modpath}."/log/";
  4995. my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def);
  4996. $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/);
  4997. my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0);
  4998. my $memory_limit = AttrVal($name, "dumpMemlimit", 100000);
  4999. my $my_comment = AttrVal($name, "dumpComment", "");
  5000. my $dumpspeed = AttrVal($name, "dumpSpeed", 10000);
  5001. my $ebd = AttrVal($name, "executeBeforeProc", undef);
  5002. my $ead = AttrVal($name, "executeAfterProc", undef);
  5003. my $mysql_commentstring = "-- ";
  5004. my $character_set = "utf8";
  5005. my $repver = $hash->{VERSION};
  5006. my $sql_text = '';
  5007. my $sql_file = '';
  5008. my $dbpraefix = "";
  5009. my ($dbh,$sth,$tablename,$sql_create,$rct,$insert,$first_insert,$backupfile,$drc,$drh,
  5010. $sql_daten,$inhalt,$filesize,$totalrecords,$status_start,$status_end,$err,$db_MB_start,$db_MB_end);
  5011. my (@ar,@tablerecords,@tablenames,@tables,@ergebnis);
  5012. my (%db_tables);
  5013. # Background-Startzeit
  5014. my $bst = [gettimeofday];
  5015. Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname'");
  5016. ##################### Beginn Dump ########################
  5017. ##############################################################
  5018. undef(%db_tables);
  5019. # Startzeit ermitteln
  5020. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  5021. $Jahr += 1900;
  5022. $Monat += 1;
  5023. $Jahrestag += 1;
  5024. my $CTIME_String = strftime "%Y-%m-%d %T",localtime(time);
  5025. my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten);
  5026. my $starttime = sprintf("%02d",$Monatstag).".".sprintf("%02d",$Monat).".".$Jahr." ".sprintf("%02d",$Stunden).":".sprintf("%02d",$Minuten);
  5027. my $fieldlist = "";
  5028. # Verbindung mit DB
  5029. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  5030. if ($@) {
  5031. $err = encode_base64($@,"");
  5032. Log3 ($name, 2, "DbRep $name - $@");
  5033. return "$name|''|$err|''|''|''|''|''|''|''";
  5034. }
  5035. # SQL-Startzeit
  5036. my $st = [gettimeofday];
  5037. ##################### Mysql-Version ermitteln ########################
  5038. eval { $sth = $dbh->prepare("SELECT VERSION()");
  5039. $sth->execute;
  5040. };
  5041. if ($@) {
  5042. $err = encode_base64($@,"");
  5043. Log3 ($name, 2, "DbRep $name - $@");
  5044. $dbh->disconnect;
  5045. return "$name|''|$err|''|''|''|''|''|''|''";
  5046. }
  5047. my @mysql_version = $sth->fetchrow;
  5048. my @v = split(/\./,$mysql_version[0]);
  5049. if($v[0] >= 5 || ($v[0] >= 4 && $v[1] >= 1) ) {
  5050. # mysql Version >= 4.1
  5051. $sth = $dbh->prepare("SET NAMES '".$character_set."'");
  5052. $sth->execute;
  5053. # get standard encoding of MySQl-Server
  5054. $sth = $dbh->prepare("SHOW VARIABLES LIKE 'character_set_connection'");
  5055. $sth->execute;
  5056. @ar = $sth->fetchrow;
  5057. $character_set = $ar[1];
  5058. } else {
  5059. # mysql Version < 4.1 -> no SET NAMES available
  5060. # get standard encoding of MySQl-Server
  5061. $sth = $dbh->prepare("SHOW VARIABLES LIKE 'character_set'");
  5062. $sth->execute;
  5063. @ar = $sth->fetchrow;
  5064. if (defined($ar[1])) { $character_set=$ar[1]; }
  5065. }
  5066. Log3 ($name, 3, "DbRep $name - Characterset of collection and backup file set to $character_set. ");
  5067. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  5068. undef(@tables);
  5069. undef(@tablerecords);
  5070. my %db_tables_views;
  5071. my $t = 0;
  5072. my $r = 0;
  5073. my $st_e = "\n";
  5074. my $value = 0;
  5075. my $engine = '';
  5076. my $query ="SHOW TABLE STATUS FROM `$dbname`";
  5077. Log3 ($name, 5, "DbRep $name - current query: $query ");
  5078. if ($dbpraefix ne "") {
  5079. $query.=" LIKE '$dbpraefix%'";
  5080. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname with prefix $dbpraefix....");
  5081. } else {
  5082. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  5083. }
  5084. eval { $sth = $dbh->prepare($query);
  5085. $sth->execute;
  5086. };
  5087. if ($@) {
  5088. $err = encode_base64($@,"");
  5089. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  5090. $dbh->disconnect;
  5091. return "$name|''|$err|''|''|''|''|''|''|''";
  5092. }
  5093. while ( $value = $sth->fetchrow_hashref()) {
  5094. $value->{skip_data} = 0; #defaut -> backup data of table
  5095. # verbose 5 logging
  5096. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  5097. foreach my $tk (sort(keys(%$value))) {
  5098. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  5099. }
  5100. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  5101. # decide if we need to skip the data while dumping (VIEWs and MEMORY)
  5102. # check for old MySQL3-Syntax Type=xxx
  5103. if (defined $value->{Type}) {
  5104. # port old index type to index engine, so we can use the index Engine in the rest of the script
  5105. $value->{Engine} = $value->{Type};
  5106. $engine = uc($value->{Type});
  5107. if ($engine eq "MEMORY") {
  5108. $value->{skip_data} = 1;
  5109. }
  5110. }
  5111. # check for > MySQL3 Engine = xxx
  5112. if (defined $value->{Engine}) {
  5113. $engine = uc($value->{Engine});
  5114. if ($engine eq "MEMORY") {
  5115. $value->{skip_data} = 1;
  5116. }
  5117. }
  5118. # check for Views - if it is a view the comment starts with "VIEW"
  5119. if (defined $value->{Comment} && uc(substr($value->{Comment},0,4)) eq 'VIEW') {
  5120. $value->{skip_data} = 1;
  5121. $value->{Engine} = 'VIEW';
  5122. $value->{Update_time} = '';
  5123. $db_tables_views{$value->{Name}} = $value;
  5124. } else {
  5125. $db_tables{$value->{Name}} = $value;
  5126. }
  5127. # cast indexes to int, cause they are used for builing the statusline
  5128. $value->{Rows} += 0;
  5129. $value->{Data_length} += 0;
  5130. $value->{Index_length} += 0;
  5131. }
  5132. $sth->finish;
  5133. @tablenames = sort(keys(%db_tables));
  5134. # add VIEW at the end as they need all tables to be created before
  5135. @tablenames = (@tablenames,sort(keys(%db_tables_views)));
  5136. %db_tables = (%db_tables,%db_tables_views);
  5137. $tablename = '';
  5138. if (@tablenames < 1) {
  5139. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  5140. Log3 ($name, 2, "DbRep $name - $err");
  5141. $err = encode_base64($@,"");
  5142. $dbh->disconnect;
  5143. return "$name|''|$err|''|''|''|''|''|''|''";
  5144. }
  5145. if($optimize_tables_beforedump) {
  5146. # Tabellen optimieren vor dem Dump
  5147. $hash->{HELPER}{DBTABLES} = \%db_tables;
  5148. ($err,$db_MB_start,$db_MB_end) = DbRep_mysqlOptimizeTables($hash,$dbh,@tablenames);
  5149. if ($err) {
  5150. $err = encode_base64($err,"");
  5151. return "$name|''|$err|''|''|''|''|''|''|''";
  5152. }
  5153. }
  5154. # Tabelleneigenschaften für SQL-File ermitteln
  5155. $st_e .= "-- TABLE-INFO\n";
  5156. foreach $tablename (@tablenames) {
  5157. my $dump_table = 1;
  5158. if ($dbpraefix ne "") {
  5159. if (substr($tablename,0,length($dbpraefix)) ne $dbpraefix) {
  5160. # exclude table from backup because it doesn't fit to praefix
  5161. $dump_table = 0;
  5162. }
  5163. }
  5164. if ($dump_table == 1) {
  5165. # how many rows
  5166. $sql_create = "SELECT count(*) FROM `$tablename`";
  5167. eval { $sth = $dbh->prepare($sql_create);
  5168. $sth->execute;
  5169. };
  5170. if ($@) {
  5171. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  5172. Log3 ($name, 2, "DbRep $name - $err");
  5173. $err = encode_base64($@,"");
  5174. $dbh->disconnect;
  5175. return "$name|''|$err|''|''|''|''|''|''|''";
  5176. }
  5177. $db_tables{$tablename}{Rows} = $sth->fetchrow;
  5178. $sth->finish;
  5179. $r += $db_tables{$tablename}{Rows};
  5180. push(@tables,$db_tables{$tablename}{Name}); # add tablename to backuped tables
  5181. $t++;
  5182. if (!defined $db_tables{$tablename}{Update_time}) {
  5183. $db_tables{$tablename}{Update_time} = 0;
  5184. }
  5185. $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";
  5186. if($db_tables{$tablename}{Name} eq "current") {
  5187. $drc = $db_tables{$tablename}{Rows};
  5188. }
  5189. if($db_tables{$tablename}{Name} eq "history") {
  5190. $drh = $db_tables{$tablename}{Rows};
  5191. }
  5192. }
  5193. }
  5194. $st_e .= "-- EOF TABLE-INFO";
  5195. Log3 ($name, 3, "DbRep $name - Found ".(@tables)." tables with $r records.");
  5196. # AUFBAU der Statuszeile in SQL-File:
  5197. # -- Status | tabellenzahl | datensaetze | Datenbankname | Kommentar | MySQLVersion | Charset | EXTINFO
  5198. #
  5199. $status_start = $mysql_commentstring."Status | Tables: $t | Rows: $r ";
  5200. $status_end = "| DB: $dbname | Comment: $my_comment | MySQL-Version: $mysql_version[0] ";
  5201. $status_end .= "| Charset: $character_set $st_e\n".
  5202. $mysql_commentstring."Dump created on $CTIME_String by DbRep-Version $repver\n".$mysql_commentstring;
  5203. $sql_text = $status_start.$status_end;
  5204. # neues SQL-Ausgabefile anlegen
  5205. ($sql_text,$first_insert,$sql_file,$backupfile,$err) = DbRep_NewDumpFilename($sql_text,$dump_path,$dbname,$time_stamp,$character_set);
  5206. if ($err) {
  5207. Log3 ($name, 2, "DbRep $name - $err");
  5208. $err = encode_base64($err,"");
  5209. return "$name|''|$err|''|''|''|''|''|''|''";
  5210. } else {
  5211. Log3 ($name, 5, "DbRep $name - New dumpfile $sql_file has been created.");
  5212. }
  5213. ##################### jede einzelne Tabelle dumpen ########################
  5214. $totalrecords = 0;
  5215. foreach $tablename (@tables) {
  5216. # first get CREATE TABLE Statement
  5217. if($dbpraefix eq "" || ($dbpraefix ne "" && substr($tablename,0,length($dbpraefix)) eq $dbpraefix)) {
  5218. Log3 ($name, 3, "DbRep $name - Dumping table $tablename (Type ".$db_tables{$tablename}{Engine}."):");
  5219. $a = "\n\n$mysql_commentstring\n$mysql_commentstring"."Table structure for table `$tablename`\n$mysql_commentstring\n";
  5220. if ($db_tables{$tablename}{Engine} ne 'VIEW' ) {
  5221. $a .= "DROP TABLE IF EXISTS `$tablename`;\n";
  5222. } else {
  5223. $a .= "DROP VIEW IF EXISTS `$tablename`;\n";
  5224. }
  5225. $sql_text .= $a;
  5226. $sql_create = "SHOW CREATE TABLE `$tablename`";
  5227. Log3 ($name, 5, "DbRep $name - current query: $sql_create ");
  5228. eval { $sth = $dbh->prepare($sql_create);
  5229. $sth->execute;
  5230. };
  5231. if ($@) {
  5232. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  5233. Log3 ($name, 2, "DbRep $name - $err");
  5234. $err = encode_base64($@,"");
  5235. $dbh->disconnect;
  5236. return "$name|''|$err|''|''|''|''|''|''|''";
  5237. }
  5238. @ergebnis = $sth->fetchrow;
  5239. $sth->finish;
  5240. $a = $ergebnis[1].";\n";
  5241. if (length($a) < 10) {
  5242. $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;
  5243. Log3 ($name, 2, "DbRep $name - $err");
  5244. } else {
  5245. $sql_text .= $a;
  5246. # verbose 5 logging
  5247. Log3 ($name, 5, "DbRep $name - Create-SQL found:\n$a");
  5248. }
  5249. if ($db_tables{$tablename}{skip_data} == 0) {
  5250. $sql_text .= "\n$mysql_commentstring\n$mysql_commentstring"."Dumping data for table `$tablename`\n$mysql_commentstring\n";
  5251. $sql_text .= "/*!40000 ALTER TABLE `$tablename` DISABLE KEYS */;";
  5252. DbRep_WriteToDumpFile($sql_text,$sql_file);
  5253. $sql_text = "";
  5254. # build fieldlist
  5255. $fieldlist = "(";
  5256. $sql_create = "SHOW FIELDS FROM `$tablename`";
  5257. Log3 ($name, 5, "DbRep $name - current query: $sql_create ");
  5258. eval { $sth = $dbh->prepare($sql_create);
  5259. $sth->execute;
  5260. };
  5261. if ($@) {
  5262. $err = "Fatal error sending Query '".$sql_create."' ! MySQL-Error: ".$@;
  5263. Log3 ($name, 2, "DbRep $name - $err");
  5264. $err = encode_base64($@,"");
  5265. $dbh->disconnect;
  5266. return "$name|''|$err|''|''|''|''|''|''|''";
  5267. }
  5268. while (@ar = $sth->fetchrow) {
  5269. $fieldlist .= "`".$ar[0]."`,";
  5270. }
  5271. $sth->finish;
  5272. # verbose 5 logging
  5273. Log3 ($name, 5, "DbRep $name - Fieldlist found: $fieldlist");
  5274. # remove trailing ',' and add ')'
  5275. $fieldlist = substr($fieldlist,0,length($fieldlist)-1).")";
  5276. # how many rows
  5277. $rct = $db_tables{$tablename}{Rows};
  5278. Log3 ($name, 5, "DbRep $name - Number entries of table $tablename: $rct");
  5279. # create insert Statements
  5280. for (my $ttt = 0; $ttt < $rct; $ttt += $dumpspeed) {
  5281. # default beginning for INSERT-String
  5282. $insert = "INSERT INTO `$tablename` $fieldlist VALUES (";
  5283. $first_insert = 0;
  5284. # get rows (parts)
  5285. $sql_daten = "SELECT * FROM `$tablename` LIMIT ".$ttt.",".$dumpspeed.";";
  5286. eval { $sth = $dbh->prepare($sql_daten);
  5287. $sth->execute;
  5288. };
  5289. if ($@) {
  5290. $err = "Fatal error sending Query '".$sql_daten."' ! MySQL-Error: ".$@;
  5291. Log3 ($name, 2, "DbRep $name - $err");
  5292. $err = encode_base64($@,"");
  5293. $dbh->disconnect;
  5294. return "$name|''|$err|''|''|''|''|''|''|''";
  5295. }
  5296. while ( @ar = $sth->fetchrow) {
  5297. #Start the insert
  5298. if($first_insert == 0) {
  5299. $a = "\n$insert";
  5300. } else {
  5301. $a = "\n(";
  5302. }
  5303. # quote all values
  5304. foreach $inhalt(@ar) { $a .= $dbh->quote($inhalt).","; }
  5305. # remove trailing ',' and add end-sql
  5306. $a = substr($a,0, length($a)-1).");";
  5307. $sql_text .= $a;
  5308. if($memory_limit > 0 && length($sql_text) > $memory_limit) {
  5309. ($filesize,$err) = DbRep_WriteToDumpFile($sql_text,$sql_file);
  5310. # Log3 ($name, 5, "DbRep $name - Memory limit '$memory_limit' exceeded. Wrote to '$sql_file'. Filesize: '".DbRep_byteOutput($filesize)."'");
  5311. $sql_text = "";
  5312. }
  5313. }
  5314. $sth->finish;
  5315. }
  5316. $sql_text .= "\n/*!40000 ALTER TABLE `$tablename` ENABLE KEYS */;\n";
  5317. }
  5318. # write sql commands to file
  5319. ($filesize,$err) = DbRep_WriteToDumpFile($sql_text,$sql_file);
  5320. $sql_text = "";
  5321. if ($db_tables{$tablename}{skip_data} == 0) {
  5322. Log3 ($name, 3, "DbRep $name - $rct records inserted (size of backupfile: ".DbRep_byteOutput($filesize).")");
  5323. $totalrecords += $rct;
  5324. } else {
  5325. Log3 ($name, 3, "DbRep $name - Dumping structure of $tablename (Type ".$db_tables{$tablename}{Engine}." ) (size of backupfile: ".DbRep_byteOutput($filesize).")");
  5326. }
  5327. }
  5328. }
  5329. # end
  5330. DbRep_WriteToDumpFile("\nSET FOREIGN_KEY_CHECKS=1;\n",$sql_file);
  5331. ($filesize,$err) = DbRep_WriteToDumpFile($mysql_commentstring."EOB\n",$sql_file);
  5332. # Datenbankverbindung schliessen
  5333. $sth->finish() if (defined $sth);
  5334. $dbh->disconnect();
  5335. # SQL-Laufzeit ermitteln
  5336. my $rt = tv_interval($st);
  5337. # Dumpfile komprimieren wenn dumpCompress=1
  5338. my $compress = AttrVal($name,"dumpCompress",0);
  5339. if($compress) {
  5340. # $err nicht auswerten -> wenn compress fehlerhaft wird unkomprimiertes dumpfile verwendet
  5341. ($err,$backupfile) = DbRep_dumpCompress($hash,$backupfile);
  5342. $filesize = (stat("$dump_path$backupfile"))[7];
  5343. }
  5344. # Dumpfile per FTP senden und versionieren
  5345. my ($ftperr,$ftpmsg,@ftpfd) = DbRep_sendftp($hash,$backupfile);
  5346. my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0;
  5347. my $ffd = join(", ", @ftpfd);
  5348. $ffd = $ffd?encode_base64($ffd,""):0;
  5349. # alte Dumpfiles löschen
  5350. my @fd = DbRep_deldumpfiles($hash,$backupfile);
  5351. my $bfd = join(", ", @fd );
  5352. $bfd = $bfd?encode_base64($bfd,""):0;
  5353. # Background-Laufzeit ermitteln
  5354. my $brt = tv_interval($bst);
  5355. $rt = $rt.",".$brt;
  5356. my $fsize = DbRep_byteOutput($filesize);
  5357. $fsize = encode_base64($fsize,"");
  5358. Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname, total time used: ".sprintf("%.0f",$brt)." sec.");
  5359. return "$name|$rt|''|$dump_path$backupfile|$drc|$drh|$fsize|$ftp|$bfd|$ffd";
  5360. }
  5361. ####################################################################################################
  5362. # nicht blockierende Dump-Routine für MySQL (serverSide)
  5363. ####################################################################################################
  5364. sub mysql_DoDumpServerSide($) {
  5365. my ($name) = @_;
  5366. my $hash = $defs{$name};
  5367. my $dbloghash = $hash->{dbloghash};
  5368. my $dbconn = $dbloghash->{dbconn};
  5369. my $dbuser = $dbloghash->{dbuser};
  5370. my $dblogname = $dbloghash->{NAME};
  5371. my $dbpassword = $attr{"sec$dblogname"}{secret};
  5372. my $dbname = $hash->{DATABASE};
  5373. my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0);
  5374. my $dump_path_rem = AttrVal($name, "dumpDirRemote", "./");
  5375. $dump_path_rem = $dump_path_rem."/" unless($dump_path_rem =~ m/\/$/);
  5376. my $ebd = AttrVal($name, "executeBeforeProc", undef);
  5377. my $ead = AttrVal($name, "executeAfterProc", undef);
  5378. my $table = "history";
  5379. my ($dbh,$sth,$err,$db_MB_start,$db_MB_end,$drh);
  5380. my (%db_tables,@tablenames);
  5381. # Background-Startzeit
  5382. my $bst = [gettimeofday];
  5383. # Verbindung mit DB
  5384. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  5385. if ($@) {
  5386. $err = encode_base64($@,"");
  5387. Log3 ($name, 2, "DbRep $name - $@");
  5388. return "$name|''|$err|''|''|''|''|''|''|''";
  5389. }
  5390. # Eigenschaften der vorhandenen Tabellen ermitteln (SHOW TABLE STATUS -> Rows sind nicht exakt !!)
  5391. my $value = 0;
  5392. my $query ="SHOW TABLE STATUS FROM `$dbname`";
  5393. Log3 ($name, 5, "DbRep $name - current query: $query ");
  5394. Log3 ($name, 3, "DbRep $name - Searching for tables inside database $dbname....");
  5395. eval { $sth = $dbh->prepare($query);
  5396. $sth->execute;
  5397. };
  5398. if ($@) {
  5399. $err = encode_base64($@,"");
  5400. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  5401. $dbh->disconnect;
  5402. return "$name|''|$err|''|''|''|''|''|''|''";
  5403. }
  5404. while ( $value = $sth->fetchrow_hashref()) {
  5405. # verbose 5 logging
  5406. Log3 ($name, 5, "DbRep $name - ......... Table definition found: .........");
  5407. foreach my $tk (sort(keys(%$value))) {
  5408. Log3 ($name, 5, "DbRep $name - $tk: $value->{$tk}") if(defined($value->{$tk}) && $tk ne "Rows");
  5409. }
  5410. Log3 ($name, 5, "DbRep $name - ......... Table definition END ............");
  5411. # check for old MySQL3-Syntax Type=xxx
  5412. if (defined $value->{Type}) {
  5413. # port old index type to index engine, so we can use the index Engine in the rest of the script
  5414. $value->{Engine} = $value->{Type};
  5415. }
  5416. $db_tables{$value->{Name}} = $value;
  5417. }
  5418. $sth->finish;
  5419. @tablenames = sort(keys(%db_tables));
  5420. if (@tablenames < 1) {
  5421. $err = "There are no tables inside database $dbname ! It doesn't make sense to backup an empty database. Skipping this one.";
  5422. Log3 ($name, 2, "DbRep $name - $err");
  5423. $err = encode_base64($@,"");
  5424. $dbh->disconnect;
  5425. return "$name|''|$err|''|''|''|''|''|''|''";
  5426. }
  5427. if($optimize_tables_beforedump) {
  5428. # Tabellen optimieren vor dem Dump
  5429. $hash->{HELPER}{DBTABLES} = \%db_tables;
  5430. ($err,$db_MB_start,$db_MB_end) = DbRep_mysqlOptimizeTables($hash,$dbh,@tablenames);
  5431. if ($err) {
  5432. $err = encode_base64($err,"");
  5433. return "$name|''|$err|''|''|''|''|''|''|''";
  5434. }
  5435. }
  5436. Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname', table '$table'");
  5437. # Startzeit ermitteln
  5438. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  5439. $Jahr += 1900;
  5440. $Monat += 1;
  5441. $Jahrestag += 1;
  5442. my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten);
  5443. my $bfile = $dbname."_".$table."_".$time_stamp.".csv";
  5444. Log3 ($name, 5, "DbRep $name - Use Outfile: $dump_path_rem$bfile");
  5445. # SQL-Startzeit
  5446. my $st = [gettimeofday];
  5447. my $sql = "SELECT * FROM history INTO OUTFILE '$dump_path_rem$bfile' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n'; ";
  5448. eval {$sth = $dbh->prepare($sql);
  5449. $drh = $sth->execute();
  5450. };
  5451. if ($@) {
  5452. # error bei sql-execute
  5453. $err = encode_base64($@,"");
  5454. Log3 ($name, 2, "DbRep $name - $@");
  5455. $dbh->disconnect;
  5456. return "$name|''|$err|''|''|''|''|''|''|''";
  5457. }
  5458. $sth->finish;
  5459. $dbh->disconnect;
  5460. # SQL-Laufzeit ermitteln
  5461. my $rt = tv_interval($st);
  5462. # Dumpfile komprimieren wenn dumpCompress=1
  5463. my $compress = AttrVal($name,"dumpCompress",0);
  5464. if($compress) {
  5465. # $err nicht auswerten -> wenn compress fehlerhaft wird unkomprimiertes dumpfile verwendet
  5466. ($err,$bfile) = DbRep_dumpCompress($hash,$bfile);
  5467. }
  5468. # Größe Dumpfile ermitteln ("dumpDirRemote" muß auf "dumpDirLocal" gemountet sein)
  5469. my $dump_path_def = $attr{global}{modpath}."/log/";
  5470. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  5471. $dump_path_loc = $dump_path_loc."/" unless($dump_path_loc =~ m/\/$/);
  5472. my $filesize = (stat($dump_path_loc.$bfile))[7]?(stat($dump_path_loc.$bfile))[7]:"n.a.";
  5473. Log3 ($name, 3, "DbRep $name - Number of exported datasets: $drh");
  5474. Log3 ($name, 3, "DbRep $name - Size of backupfile: ".DbRep_byteOutput($filesize));
  5475. # Dumpfile per FTP senden und versionieren
  5476. my ($ftperr,$ftpmsg,@ftpfd) = DbRep_sendftp($hash,$bfile);
  5477. my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0;
  5478. my $ffd = join(", ", @ftpfd);
  5479. $ffd = $ffd?encode_base64($ffd,""):0;
  5480. # alte Dumpfiles löschen
  5481. my @fd = DbRep_deldumpfiles($hash,$bfile);
  5482. my $bfd = join(", ", @fd );
  5483. $bfd = $bfd?encode_base64($bfd,""):0;
  5484. # Background-Laufzeit ermitteln
  5485. my $brt = tv_interval($bst);
  5486. my $fsize = DbRep_byteOutput($filesize);
  5487. $fsize = encode_base64($fsize,"");
  5488. $rt = $rt.",".$brt;
  5489. Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname - total time used: ".sprintf("%.0f",$brt)." seconds");
  5490. return "$name|$rt|''|$dump_path_rem$bfile|n.a.|$drh|$fsize|$ftp|$bfd|$ffd";
  5491. }
  5492. ####################################################################################################
  5493. # Dump-Routine SQLite
  5494. ####################################################################################################
  5495. sub DbRep_sqliteDoDump($) {
  5496. my ($name) = @_;
  5497. my $hash = $defs{$name};
  5498. my $dbloghash = $hash->{dbloghash};
  5499. my $dbname = $hash->{DATABASE};
  5500. my $dbconn = $dbloghash->{dbconn};
  5501. my $dbuser = $dbloghash->{dbuser};
  5502. my $dblogname = $dbloghash->{NAME};
  5503. my $dbpassword = $attr{"sec$dblogname"}{secret};
  5504. my $dump_path_def = $attr{global}{modpath}."/log/";
  5505. my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def);
  5506. $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/);
  5507. my $optimize_tables_beforedump = AttrVal($name, "optimizeTablesBeforeDump", 0);
  5508. my $ebd = AttrVal($name, "executeBeforeProc", undef);
  5509. my $ead = AttrVal($name, "executeAfterProc", undef);
  5510. my ($dbh,$err,$db_MB,$r,$query,$sth);
  5511. # Background-Startzeit
  5512. my $bst = [gettimeofday];
  5513. # Verbindung mit DB
  5514. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  5515. if ($@) {
  5516. $err = encode_base64($@,"");
  5517. Log3 ($name, 2, "DbRep $name - $@");
  5518. return "$name|''|$err|''|''|''|''|''|''|''";
  5519. }
  5520. if($optimize_tables_beforedump) {
  5521. # Vacuum vor Dump
  5522. # Anfangsgröße ermitteln
  5523. $db_MB = (split(' ',qx(du -m $dbname)))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  5524. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB");
  5525. $query ="VACUUM";
  5526. Log3 ($name, 5, "DbRep $name - current query: $query ");
  5527. Log3 ($name, 3, "DbRep $name - VACUUM database $dbname....");
  5528. eval {$sth = $dbh->prepare($query);
  5529. $r = $sth->execute();
  5530. };
  5531. if ($@) {
  5532. $err = encode_base64($@,"");
  5533. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! SQLite-Error: ".$@);
  5534. $sth->finish;
  5535. $dbh->disconnect;
  5536. return "$name|''|$err|''|''|''|''|''|''|''";
  5537. }
  5538. # Endgröße ermitteln
  5539. $db_MB = (split(' ',qx(du -m $dbname)))[0] if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  5540. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB");
  5541. }
  5542. $dbname = (split /[\/]/, $dbname)[-1];
  5543. Log3 ($name, 3, "DbRep $name - Starting dump of database '$dbname'");
  5544. # Startzeit ermitteln
  5545. my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time);
  5546. $Jahr += 1900;
  5547. $Monat += 1;
  5548. $Jahrestag += 1;
  5549. my $time_stamp = $Jahr."_".sprintf("%02d",$Monat)."_".sprintf("%02d",$Monatstag)."_".sprintf("%02d",$Stunden)."_".sprintf("%02d",$Minuten);
  5550. $dbname = (split /\./, $dbname)[0];
  5551. my $bfile = $dbname."_".$time_stamp.".sqlitebkp";
  5552. Log3 ($name, 5, "DbRep $name - Use Outfile: $dump_path$bfile");
  5553. # SQL-Startzeit
  5554. my $st = [gettimeofday];
  5555. eval { $dbh->sqlite_backup_to_file($dump_path.$bfile); };
  5556. if ($@) {
  5557. $err = encode_base64($@,"");
  5558. Log3 ($name, 2, "DbRep $name - $@");
  5559. $dbh->disconnect;
  5560. return "$name|''|$err|''|''|''|''|''|''|''";
  5561. }
  5562. $dbh->disconnect;
  5563. # SQL-Laufzeit ermitteln
  5564. my $rt = tv_interval($st);
  5565. # Dumpfile komprimieren
  5566. my $compress = AttrVal($name,"dumpCompress",0);
  5567. if($compress) {
  5568. # $err nicht auswerten -> wenn compress fehlerhaft wird unkomprimiertes dumpfile verwendet
  5569. ($err,$bfile) = DbRep_dumpCompress($hash,$bfile);
  5570. }
  5571. # Größe Dumpfile ermitteln
  5572. my @a = split(' ',qx(du $dump_path$bfile)) if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  5573. my $filesize = ($a[0])?($a[0]*1024):"n.a.";
  5574. my $fsize = DbRep_byteOutput($filesize);
  5575. Log3 ($name, 3, "DbRep $name - Size of backupfile: ".$fsize);
  5576. # Dumpfile per FTP senden und versionieren
  5577. my ($ftperr,$ftpmsg,@ftpfd) = DbRep_sendftp($hash,$bfile);
  5578. my $ftp = $ftperr?encode_base64($ftperr,""):$ftpmsg?encode_base64($ftpmsg,""):0;
  5579. my $ffd = join(", ", @ftpfd);
  5580. $ffd = $ffd?encode_base64($ffd,""):0;
  5581. # alte Dumpfiles löschen
  5582. my @fd = DbRep_deldumpfiles($hash,$bfile);
  5583. my $bfd = join(", ", @fd );
  5584. $bfd = $bfd?encode_base64($bfd,""):0;
  5585. # Background-Laufzeit ermitteln
  5586. my $brt = tv_interval($bst);
  5587. $fsize = encode_base64($fsize,"");
  5588. $rt = $rt.",".$brt;
  5589. Log3 ($name, 3, "DbRep $name - Finished backup of database $dbname - total time used: ".sprintf("%.0f",$brt)." seconds");
  5590. return "$name|$rt|''|$dump_path$bfile|n.a.|n.a.|$fsize|$ftp|$bfd|$ffd";
  5591. }
  5592. ####################################################################################################
  5593. # Auswertungsroutine der nicht blockierenden DB-Funktion Dump
  5594. ####################################################################################################
  5595. sub DbRep_DumpDone($) {
  5596. my ($string) = @_;
  5597. my @a = split("\\|",$string);
  5598. my $hash = $defs{$a[0]};
  5599. my $bt = $a[1];
  5600. my ($rt,$brt) = split(",", $bt);
  5601. my $err = $a[2]?decode_base64($a[2]):undef;
  5602. my $bfile = $a[3];
  5603. my $drc = $a[4];
  5604. my $drh = $a[5];
  5605. my $fs = $a[6]?decode_base64($a[6]):undef;
  5606. my $ftp = $a[7]?decode_base64($a[7]):undef;
  5607. my $bfd = $a[8]?decode_base64($a[8]):undef;
  5608. my $ffd = $a[9]?decode_base64($a[9]):undef;
  5609. my $name = $hash->{NAME};
  5610. my $erread;
  5611. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  5612. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  5613. if ($err) {
  5614. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  5615. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  5616. return;
  5617. }
  5618. # only for this block because of warnings if details of readings are not set
  5619. no warnings 'uninitialized';
  5620. readingsBeginUpdate($hash);
  5621. ReadingsBulkUpdateValue($hash, "DumpFileCreated", $bfile);
  5622. ReadingsBulkUpdateValue($hash, "DumpFileCreatedSize", $fs);
  5623. ReadingsBulkUpdateValue($hash, "DumpFilesDeleted", $bfd);
  5624. ReadingsBulkUpdateValue($hash, "DumpRowsCurrent", $drc);
  5625. ReadingsBulkUpdateValue($hash, "DumpRowsHistory", $drh);
  5626. ReadingsBulkUpdateValue($hash, "FTP_Message", $ftp) if($ftp);
  5627. ReadingsBulkUpdateValue($hash, "FTP_DumpFilesDeleted", $ffd) if($ffd);
  5628. ReadingsBulkUpdateValue($hash, "background_processing_time", sprintf("%.4f",$brt));
  5629. readingsEndUpdate($hash, 1);
  5630. # Befehl nach Procedure ausführen
  5631. $erread = DbRep_afterproc($hash, "dump");
  5632. my $state = $erread?$erread:"Database backup finished";
  5633. readingsBeginUpdate($hash);
  5634. ReadingsBulkUpdateTimeState($hash,undef,undef,$state);
  5635. readingsEndUpdate($hash, 1);
  5636. Log3 ($name, 3, "DbRep $name - Database dump finished successfully. ");
  5637. return;
  5638. }
  5639. ####################################################################################################
  5640. # Dump-Routine SQLite
  5641. ####################################################################################################
  5642. sub DbRep_sqliteRepair($) {
  5643. my ($name) = @_;
  5644. my $hash = $defs{$name};
  5645. my $dbloghash = $hash->{dbloghash};
  5646. my $db = $hash->{DATABASE};
  5647. my $dbname = (split /[\/]/, $db)[-1];
  5648. my $dbpath = (split /$dbname/, $db)[0];
  5649. my $dblogname = $dbloghash->{NAME};
  5650. my $sqlfile = $dbpath."dump_all.sql";
  5651. my ($c,$clog,$ret,$err);
  5652. # Background-Startzeit
  5653. my $bst = [gettimeofday];
  5654. $c = "echo \".mode insert\n.output $sqlfile\n.dump\n.exit\" | sqlite3 $db; ";
  5655. $clog = $c;
  5656. $clog =~ s/\n/ /g;
  5657. Log3 ($name, 4, "DbRep $name - Systemcall: $clog");
  5658. $ret = system qq($c);
  5659. if($ret) {
  5660. $err = "Error in step \"dump corrupt database\" - see logfile";
  5661. $err = encode_base64($err,"");
  5662. return "$name|''|$err";
  5663. }
  5664. $c = "mv $db $db.corrupt";
  5665. $clog = $c;
  5666. $clog =~ s/\n/ /g;
  5667. Log3 ($name, 4, "DbRep $name - Systemcall: $clog");
  5668. $ret = system qq($c);
  5669. if($ret) {
  5670. $err = "Error in step \"move atabase to corrupt-db\" - see logfile";
  5671. $err = encode_base64($err,"");
  5672. return "$name|''|$err";
  5673. }
  5674. $c = "echo \".read $sqlfile\n.exit\" | sqlite3 $db;";
  5675. $clog = $c;
  5676. $clog =~ s/\n/ /g;
  5677. Log3 ($name, 4, "DbRep $name - Systemcall: $clog");
  5678. $ret = system qq($c);
  5679. if($ret) {
  5680. $err = "Error in step \"read dump to new database\" - see logfile";
  5681. $err = encode_base64($err,"");
  5682. return "$name|''|$err";
  5683. }
  5684. $c = "rm $sqlfile";
  5685. $clog = $c;
  5686. $clog =~ s/\n/ /g;
  5687. Log3 ($name, 4, "DbRep $name - Systemcall: $clog");
  5688. $ret = system qq($c);
  5689. if($ret) {
  5690. $err = "Error in step \"delete $sqlfile\" - see logfile";
  5691. $err = encode_base64($err,"");
  5692. return "$name|''|$err";
  5693. }
  5694. # Background-Laufzeit ermitteln
  5695. my $brt = tv_interval($bst);
  5696. return "$name|$brt|0";
  5697. }
  5698. ####################################################################################################
  5699. # Auswertungsroutine der nicht blockierenden DB-Funktion Dump
  5700. ####################################################################################################
  5701. sub DbRep_RepairDone($) {
  5702. my ($string) = @_;
  5703. my @a = split("\\|",$string);
  5704. my $hash = $defs{$a[0]};
  5705. my $brt = $a[1];
  5706. my $err = $a[2]?decode_base64($a[2]):undef;
  5707. my $dbloghash = $hash->{dbloghash};
  5708. my $name = $hash->{NAME};
  5709. my $erread;
  5710. delete($hash->{HELPER}{RUNNING_REPAIR});
  5711. # Datenbankverbindung in DbLog wieder öffenen
  5712. my $dbl = $dbloghash->{NAME};
  5713. CommandSet(undef,"$dbl reopen");
  5714. if ($err) {
  5715. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  5716. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  5717. return;
  5718. }
  5719. # only for this block because of warnings if details of readings are not set
  5720. no warnings 'uninitialized';
  5721. readingsBeginUpdate($hash);
  5722. ReadingsBulkUpdateValue($hash, "background_processing_time", sprintf("%.4f",$brt));
  5723. readingsEndUpdate($hash, 1);
  5724. # Befehl nach Procedure ausführen
  5725. $erread = DbRep_afterproc($hash, "repair");
  5726. my $state = $erread?$erread:"Repair finished $hash->{DATABASE}";
  5727. readingsBeginUpdate($hash);
  5728. ReadingsBulkUpdateTimeState($hash,undef,undef,$state);
  5729. readingsEndUpdate($hash, 1);
  5730. Log3 ($name, 3, "DbRep $name - Database repair $hash->{DATABASE} finished. - total time used: ".sprintf("%.0f",$brt)." seconds.");
  5731. return;
  5732. }
  5733. ####################################################################################################
  5734. # Restore SQLite
  5735. ####################################################################################################
  5736. sub DbRep_sqliteRestore ($) {
  5737. my ($string) = @_;
  5738. my ($name,$bfile) = split("\\|", $string);
  5739. my $hash = $defs{$name};
  5740. my $dbloghash = $hash->{dbloghash};
  5741. my $dbconn = $dbloghash->{dbconn};
  5742. my $dbuser = $dbloghash->{dbuser};
  5743. my $dblogname = $dbloghash->{NAME};
  5744. my $dbpassword = $attr{"sec$dblogname"}{secret};
  5745. my $dump_path_def = $attr{global}{modpath}."/log/";
  5746. my $dump_path = AttrVal($name, "dumpDirLocal", $dump_path_def);
  5747. $dump_path = $dump_path."/" unless($dump_path =~ m/\/$/);
  5748. my $ebd = AttrVal($name, "executeBeforeProc", undef);
  5749. my $ead = AttrVal($name, "executeAfterProc", undef);
  5750. my ($dbh,$err,$dbname);
  5751. # Background-Startzeit
  5752. my $bst = [gettimeofday];
  5753. # Verbindung mit DB
  5754. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  5755. if ($@) {
  5756. $err = encode_base64($@,"");
  5757. Log3 ($name, 2, "DbRep $name - $@");
  5758. return "$name|''|$err|''|''";
  5759. }
  5760. eval { $dbname = $dbh->sqlite_db_filename(); };
  5761. if ($@) {
  5762. $err = encode_base64($@,"");
  5763. Log3 ($name, 2, "DbRep $name - $@");
  5764. $dbh->disconnect;
  5765. return "$name|''|$err|''|''";
  5766. }
  5767. $dbname = (split /[\/]/, $dbname)[-1];
  5768. # Dumpfile dekomprimieren wenn gzip
  5769. if($bfile =~ m/.*.gzip$/) {
  5770. ($err,$bfile) = DbRep_dumpUnCompress($hash,$bfile);
  5771. if ($err) {
  5772. $err = encode_base64($err,"");
  5773. $dbh->disconnect;
  5774. return "$name|''|$err|''|''";
  5775. }
  5776. }
  5777. Log3 ($name, 3, "DbRep $name - Starting restore of database '$dbname'");
  5778. # SQL-Startzeit
  5779. my $st = [gettimeofday];
  5780. eval { $dbh->sqlite_backup_from_file($dump_path.$bfile); };
  5781. if ($@) {
  5782. $err = encode_base64($@,"");
  5783. Log3 ($name, 2, "DbRep $name - $@");
  5784. $dbh->disconnect;
  5785. return "$name|''|$err|''|''";
  5786. }
  5787. $dbh->disconnect;
  5788. # SQL-Laufzeit ermitteln
  5789. my $rt = tv_interval($st);
  5790. # Background-Laufzeit ermitteln
  5791. my $brt = tv_interval($bst);
  5792. $rt = $rt.",".$brt;
  5793. Log3 ($name, 3, "DbRep $name - Restore of $dump_path$bfile into '$dbname' finished - total time used: ".sprintf("%.0f",$brt)." seconds.");
  5794. return "$name|$rt|''|$dump_path$bfile|n.a.";
  5795. }
  5796. ####################################################################################################
  5797. # Restore MySQL (serverSide)
  5798. ####################################################################################################
  5799. sub mysql_RestoreServerSide($) {
  5800. my ($string) = @_;
  5801. my ($name, $bfile) = split("\\|", $string);
  5802. my $hash = $defs{$name};
  5803. my $dbloghash = $hash->{dbloghash};
  5804. my $dbconn = $dbloghash->{dbconn};
  5805. my $dbuser = $dbloghash->{dbuser};
  5806. my $dblogname = $dbloghash->{NAME};
  5807. my $dbpassword = $attr{"sec$dblogname"}{secret};
  5808. my $dbname = $hash->{DATABASE};
  5809. my $dump_path_rem = AttrVal($name, "dumpDirRemote", "./");
  5810. $dump_path_rem = $dump_path_rem."/" unless($dump_path_rem =~ m/\/$/);
  5811. my $table = "history";
  5812. my ($dbh,$sth,$err,$drh);
  5813. # Background-Startzeit
  5814. my $bst = [gettimeofday];
  5815. # Verbindung mit DB
  5816. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoInactiveDestroy => 1 });};
  5817. if ($@) {
  5818. $err = encode_base64($@,"");
  5819. Log3 ($name, 2, "DbRep $name - $@");
  5820. return "$name|''|$err|''|''";
  5821. }
  5822. # Dumpfile dekomprimieren wenn gzip
  5823. if($bfile =~ m/.*.gzip$/) {
  5824. ($err,$bfile) = DbRep_dumpUnCompress($hash,$bfile);
  5825. if ($err) {
  5826. $err = encode_base64($err,"");
  5827. $dbh->disconnect;
  5828. return "$name|''|$err|''|''";
  5829. }
  5830. }
  5831. Log3 ($name, 3, "DbRep $name - Starting restore of database '$dbname', table '$table'.");
  5832. # SQL-Startzeit
  5833. my $st = [gettimeofday];
  5834. my $sql = "LOAD DATA CONCURRENT INFILE '$dump_path_rem$bfile' IGNORE INTO TABLE $table FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n'; ";
  5835. eval {$sth = $dbh->prepare($sql);
  5836. $drh = $sth->execute();
  5837. };
  5838. if ($@) {
  5839. # error bei sql-execute
  5840. $err = encode_base64($@,"");
  5841. Log3 ($name, 2, "DbRep $name - $@");
  5842. $dbh->disconnect;
  5843. return "$name|''|$err|''|''";
  5844. }
  5845. $sth->finish;
  5846. $dbh->disconnect;
  5847. # SQL-Laufzeit ermitteln
  5848. my $rt = tv_interval($st);
  5849. # Background-Laufzeit ermitteln
  5850. my $brt = tv_interval($bst);
  5851. $rt = $rt.",".$brt;
  5852. Log3 ($name, 3, "DbRep $name - Restore of $dump_path_rem$bfile into '$dbname', '$table' finished - total time used: ".sprintf("%.0f",$brt)." seconds.");
  5853. return "$name|$rt|''|$dump_path_rem$bfile|n.a.";
  5854. }
  5855. ####################################################################################################
  5856. # Auswertungsroutine Restore
  5857. ####################################################################################################
  5858. sub DbRep_restoreDone($) {
  5859. my ($string) = @_;
  5860. my @a = split("\\|",$string);
  5861. my $hash = $defs{$a[0]};
  5862. my $bt = $a[1];
  5863. my ($rt,$brt) = split(",", $bt);
  5864. my $err = $a[2]?decode_base64($a[2]):undef;
  5865. my $bfile = $a[3];
  5866. my $drh = $a[4];
  5867. my $name = $hash->{NAME};
  5868. my $erread;
  5869. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  5870. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  5871. if ($err) {
  5872. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  5873. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  5874. return;
  5875. }
  5876. readingsBeginUpdate($hash);
  5877. ReadingsBulkUpdateValue($hash, "RestoreRowsHistory", $drh);
  5878. readingsEndUpdate($hash, 1);
  5879. # Befehl nach Procedure ausführen
  5880. $erread = DbRep_afterproc($hash, "restore");
  5881. my $state = $erread?$erread:"Restore of $bfile finished";
  5882. readingsBeginUpdate($hash);
  5883. ReadingsBulkUpdateTimeState($hash,$brt,undef,$state);
  5884. readingsEndUpdate($hash, 1);
  5885. Log3 ($name, 3, "DbRep $name - Database restore finished successfully. ");
  5886. return;
  5887. }
  5888. ####################################################################################################
  5889. # Übertragung Datensätze in weitere DB
  5890. ####################################################################################################
  5891. sub DbRep_syncStandby($) {
  5892. my ($string) = @_;
  5893. my ($name,$device,$reading,$runtime_string_first,$runtime_string_next,$ts,$stbyname) = split("\\§", $string);
  5894. my $hash = $defs{$name};
  5895. my $table = "history";
  5896. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  5897. my ($dbh,$dbhstby,$err,$sql,$irows,$irowdone);
  5898. # Quell-DB
  5899. my $dbloghash = $hash->{dbloghash};
  5900. my $dbconn = $dbloghash->{dbconn};
  5901. my $dbuser = $dbloghash->{dbuser};
  5902. my $dblogname = $dbloghash->{NAME};
  5903. my $dbpassword = $attr{"sec$dblogname"}{secret};
  5904. # Standby-DB
  5905. my $stbyhash = $defs{$stbyname};
  5906. my $stbyconn = $stbyhash->{dbconn};
  5907. my $stbyuser = $stbyhash->{dbuser};
  5908. my $stbypasswd = $attr{"sec$stbyname"}{secret};
  5909. my $stbyutf8 = defined($stbyhash->{UTF8})?$stbyhash->{UTF8}:0;
  5910. # Background-Startzeit
  5911. my $bst = [gettimeofday];
  5912. # Verbindung zur Quell-DB
  5913. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => $utf8 });};
  5914. if ($@) {
  5915. $err = encode_base64($@,"");
  5916. Log3 ($name, 2, "DbRep $name - $@");
  5917. return "$name|''|''|$err";
  5918. }
  5919. # Verbindung zur Standby-DB
  5920. eval {$dbhstby = DBI->connect("dbi:$stbyconn", $stbyuser, $stbypasswd, { PrintError => 0, RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => $stbyutf8 });};
  5921. if ($@) {
  5922. $err = encode_base64($@,"");
  5923. Log3 ($name, 2, "DbRep $name - $@");
  5924. return "$name|''|''|$err";
  5925. }
  5926. # ist Zeiteingrenzung und/oder Aggregation gesetzt ? (wenn ja -> "?" in SQL sonst undef)
  5927. my ($IsTimeSet,$IsAggrSet) = DbRep_checktimeaggr($hash);
  5928. Log3 ($name, 5, "DbRep $name - IsTimeSet: $IsTimeSet, IsAggrSet: $IsAggrSet");
  5929. # SQL-Startzeit
  5930. my $st = [gettimeofday];
  5931. my ($sth,$old,$new);
  5932. eval { $dbh->begin_work() if($dbh->{AutoCommit}); }; # Transaktion wenn gewünscht und autocommit ein
  5933. if ($@) {
  5934. Log3($name, 2, "DbRep $name -> Error start transaction - $@");
  5935. }
  5936. # Timestampstring to Array
  5937. my @ts = split("\\|", $ts);
  5938. Log3 ($name, 5, "DbRep $name - Timestamp-Array: \n@ts");
  5939. # DB-Abfrage zeilenweise für jeden Array-Eintrag
  5940. $irows = 0;
  5941. $irowdone = 0;
  5942. my $selspec = "TIMESTAMP,DEVICE,TYPE,EVENT,READING,VALUE,UNIT";
  5943. my $addon = '';
  5944. foreach my $row (@ts) {
  5945. my @a = split("#", $row);
  5946. my $runtime_string = $a[0];
  5947. my $runtime_string_first = $a[1];
  5948. my $runtime_string_next = $a[2];
  5949. if ($IsTimeSet || $IsAggrSet) {
  5950. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,"'$runtime_string_first'","'$runtime_string_next'",$addon);
  5951. } else {
  5952. $sql = DbRep_createSelectSql($hash,"history",$selspec,$device,$reading,undef,undef,$addon);
  5953. }
  5954. Log3 ($name, 4, "DbRep $name - SQL execute: $sql");
  5955. eval{ $sth = $dbh->prepare($sql);
  5956. $sth->execute();
  5957. };
  5958. if ($@) {
  5959. $err = encode_base64($@,"");
  5960. Log3 ($name, 2, "DbRep $name - $@");
  5961. $dbh->disconnect;
  5962. return "$name|''|''|$err";
  5963. }
  5964. no warnings 'uninitialized';
  5965. # DATE _ESC_ TIME _ESC_ DEVICE _ESC_ TYPE _ESC_ EVENT _ESC_ READING _ESC_ VALUE _ESC_ UNIT
  5966. my @row_array = map { ($_->[0] =~ s/ /_ESC_/r)."_ESC_".$_->[1]."_ESC_".$_->[2]."_ESC_".$_->[3]."_ESC_".$_->[4]."_ESC_".$_->[5]."_ESC_".$_->[6] } @{$sth->fetchall_arrayref()};
  5967. use warnings;
  5968. (undef,$irowdone,$err) = DbRep_WriteToDB($name,$dbhstby,$stbyhash,"0",@row_array) if(@row_array);
  5969. if ($err) {
  5970. Log3 ($name, 2, "DbRep $name - $err");
  5971. $err = encode_base64($err,"");
  5972. $dbh->disconnect;
  5973. $dbhstby->disconnect();
  5974. return "$name|''|''|$err";
  5975. }
  5976. $irows += $irowdone;
  5977. }
  5978. $dbh->disconnect();
  5979. $dbhstby->disconnect();
  5980. # SQL-Laufzeit ermitteln
  5981. my $rt = tv_interval($st);
  5982. # Background-Laufzeit ermitteln
  5983. my $brt = tv_interval($bst);
  5984. $rt = $rt.",".$brt;
  5985. return "$name|$irows|$rt|0";
  5986. }
  5987. ####################################################################################################
  5988. # Auswertungsroutine Übertragung Datensätze in weitere DB
  5989. ####################################################################################################
  5990. sub DbRep_syncStandbyDone($) {
  5991. my ($string) = @_;
  5992. my @a = split("\\|",$string);
  5993. my $hash = $defs{$a[0]};
  5994. my $name = $hash->{NAME};
  5995. my $irows = $a[1];
  5996. my $bt = $a[2];
  5997. my ($rt,$brt) = split(",", $bt);
  5998. my $err = $a[3]?decode_base64($a[3]):undef;
  5999. if ($err) {
  6000. ReadingsSingleUpdateValue ($hash, "errortext", $err, 1);
  6001. ReadingsSingleUpdateValue ($hash, "state", "error", 1);
  6002. delete($hash->{HELPER}{RUNNING_PID});
  6003. Log3 ($name, 4, "DbRep $name -> BlockingCall change_Done finished");
  6004. return;
  6005. }
  6006. # only for this block because of warnings if details of readings are not set
  6007. no warnings 'uninitialized';
  6008. readingsBeginUpdate($hash);
  6009. ReadingsBulkUpdateValue ($hash, "number_lines_inserted_Standby", $irows);
  6010. ReadingsBulkUpdateTimeState($hash,$brt,$rt,"done");
  6011. readingsEndUpdate($hash, 1);
  6012. # Befehl nach Procedure ausführen
  6013. my $erread = DbRep_afterproc($hash, "syncStandby");
  6014. delete($hash->{HELPER}{RUNNING_PID});
  6015. return;
  6016. }
  6017. ####################################################################################################
  6018. # Abbruchroutine Timeout Restore
  6019. ####################################################################################################
  6020. sub DbRep_restoreAborted(@) {
  6021. my ($hash,$cause) = @_;
  6022. my $name = $hash->{NAME};
  6023. my $dbh = $hash->{DBH};
  6024. my $erread;
  6025. $cause = $cause?$cause:"Timeout: process terminated";
  6026. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  6027. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  6028. # Befehl nach Procedure ausführen
  6029. no warnings 'uninitialized';
  6030. $erread = DbRep_afterproc($hash, "restore");
  6031. $erread = ", ".(split("but", $erread))[1] if($erread);
  6032. my $state = $cause.$erread;
  6033. $dbh->disconnect() if(defined($dbh));
  6034. ReadingsSingleUpdateValue ($hash, "state", $state, 1);
  6035. Log3 ($name, 2, "DbRep $name - Database restore aborted by \"$cause\" ");
  6036. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  6037. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  6038. return;
  6039. }
  6040. ####################################################################################################
  6041. # Abbruchroutine Timeout DB-Abfrage
  6042. ####################################################################################################
  6043. sub DbRep_ParseAborted(@) {
  6044. my ($hash,$cause) = @_;
  6045. my $name = $hash->{NAME};
  6046. my $dbh = $hash->{DBH};
  6047. my $erread;
  6048. $cause = $cause?$cause:"Timeout: process terminated";
  6049. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_PID}{fn} pid:$hash->{HELPER}{RUNNING_PID}{pid} $cause");
  6050. # Befehl nach Procedure ausführen
  6051. no warnings 'uninitialized';
  6052. $erread = DbRep_afterproc($hash, "command");
  6053. $erread = ", ".(split("but", $erread))[1] if($erread);
  6054. $dbh->disconnect() if(defined($dbh));
  6055. ReadingsSingleUpdateValue ($hash,"state",$cause, 1);
  6056. delete($hash->{HELPER}{RUNNING_PID});
  6057. return;
  6058. }
  6059. ####################################################################################################
  6060. # Abbruchroutine Timeout DB-Dump
  6061. ####################################################################################################
  6062. sub DbRep_DumpAborted(@) {
  6063. my ($hash,$cause) = @_;
  6064. my $name = $hash->{NAME};
  6065. my $dbh = $hash->{DBH};
  6066. my ($erread);
  6067. $cause = $cause?$cause:"Timeout: process terminated";
  6068. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BACKUP_CLIENT}{fn} pid:$hash->{HELPER}{RUNNING_BACKUP_CLIENT}{pid} $cause") if($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  6069. Log3 ($name, 1, "DbRep $name - BlockingCall $hash->{HELPER}{RUNNING_BCKPREST_SERVER}{fn} pid:$hash->{HELPER}{RUNNING_BCKPREST_SERVER}{pid} $cause") if($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  6070. # Befehl nach Procedure ausführen
  6071. no warnings 'uninitialized';
  6072. $erread = DbRep_afterproc($hash, "dump");
  6073. $erread = ", ".(split("but", $erread))[1] if($erread);
  6074. my $state = $cause.$erread;
  6075. $dbh->disconnect() if(defined($dbh));
  6076. ReadingsSingleUpdateValue ($hash, "state", $state, 1);
  6077. Log3 ($name, 2, "DbRep $name - Database dump aborted by \"$cause\" ");
  6078. delete($hash->{HELPER}{RUNNING_BACKUP_CLIENT});
  6079. delete($hash->{HELPER}{RUNNING_BCKPREST_SERVER});
  6080. return;
  6081. }
  6082. ####################################################################################################
  6083. # Abbruchroutine Timeout DB-Abfrage
  6084. ####################################################################################################
  6085. sub DbRep_OptimizeAborted(@) {
  6086. my ($hash,$cause) = @_;
  6087. my $name = $hash->{NAME};
  6088. my $dbh = $hash->{DBH};
  6089. my ($erread);
  6090. $cause = $cause?$cause:"Timeout: process terminated";
  6091. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_OPTIMIZE}}{fn} pid:$hash->{HELPER}{RUNNING_OPTIMIZE}{pid} $cause");
  6092. # Befehl nach Procedure ausführen
  6093. no warnings 'uninitialized';
  6094. $erread = DbRep_afterproc($hash, "optimize");
  6095. $erread = ", ".(split("but", $erread))[1] if($erread);
  6096. my $state = $cause.$erread;
  6097. $dbh->disconnect() if(defined($dbh));
  6098. ReadingsSingleUpdateValue ($hash, "state", $state, 1);
  6099. Log3 ($name, 2, "DbRep $name - Database optimize aborted by \"$cause\" ");
  6100. delete($hash->{HELPER}{RUNNING_OPTIMIZE});
  6101. return;
  6102. }
  6103. ####################################################################################################
  6104. # Abbruchroutine Repair SQlite
  6105. ####################################################################################################
  6106. sub DbRep_RepairAborted(@) {
  6107. my ($hash,$cause) = @_;
  6108. my $name = $hash->{NAME};
  6109. my $dbh = $hash->{DBH};
  6110. my $dbloghash = $hash->{dbloghash};
  6111. my $erread;
  6112. $cause = $cause?$cause:"Timeout: process terminated";
  6113. Log3 ($name, 1, "DbRep $name -> BlockingCall $hash->{HELPER}{RUNNING_REPAIR}{fn} pid:$hash->{HELPER}{RUNNING_REPAIR}{pid} $cause");
  6114. # Datenbankverbindung in DbLog wieder öffenen
  6115. my $dbl = $dbloghash->{NAME};
  6116. CommandSet(undef,"$dbl reopen");
  6117. # Befehl nach Procedure ausführen
  6118. no warnings 'uninitialized';
  6119. $erread = DbRep_afterproc($hash, "repair");
  6120. $erread = ", ".(split("but", $erread))[1] if($erread);
  6121. $dbh->disconnect() if(defined($dbh));
  6122. ReadingsSingleUpdateValue ($hash,"state",$cause, 1);
  6123. delete($hash->{HELPER}{RUNNING_REPAIR});
  6124. return;
  6125. }
  6126. ####################################################################################################
  6127. # SQL-Statement zusammenstellen für DB-Abfrage
  6128. ####################################################################################################
  6129. sub DbRep_createSelectSql($$$$$$$$) {
  6130. my ($hash,$table,$selspec,$device,$reading,$tf,$tn,$addon) = @_;
  6131. my $name = $hash->{NAME};
  6132. my $dbmodel = $hash->{dbloghash}{MODEL};
  6133. my ($sql,$devs,$danz,$ranz);
  6134. my $tnfull = 0;
  6135. ($devs,$danz,$reading,$ranz) = DbRep_specsForSql($hash,$device,$reading);
  6136. if($tn && $tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
  6137. $tnfull = 1;
  6138. }
  6139. $sql = "SELECT $selspec FROM $table where ";
  6140. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  6141. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  6142. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  6143. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  6144. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  6145. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  6146. if (($tf && $tn)) {
  6147. $sql .= "TIMESTAMP >= $tf AND TIMESTAMP ".($tnfull?"<=":"<")." $tn ";
  6148. } else {
  6149. if ($dbmodel eq "POSTGRESQL") {
  6150. $sql .= "true ";
  6151. } else {
  6152. $sql .= "1 ";
  6153. }
  6154. }
  6155. $sql .= "$addon;";
  6156. return $sql;
  6157. }
  6158. ####################################################################################################
  6159. # SQL-Statement zusammenstellen für DB-Updates
  6160. ####################################################################################################
  6161. sub DbRep_createUpdateSql($$$$$$$$) {
  6162. my ($hash,$table,$selspec,$device,$reading,$tf,$tn,$addon) = @_;
  6163. my $name = $hash->{NAME};
  6164. my $dbmodel = $hash->{dbloghash}{MODEL};
  6165. my ($sql,$devs,$danz,$ranz);
  6166. my $tnfull = 0;
  6167. ($devs,$danz,$reading,$ranz) = DbRep_specsForSql($hash,$device,$reading);
  6168. if($tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
  6169. $tnfull = 1;
  6170. }
  6171. $sql = "UPDATE $table SET $selspec AND ";
  6172. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  6173. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  6174. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  6175. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  6176. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  6177. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  6178. if (($tf && $tn)) {
  6179. $sql .= "TIMESTAMP >= $tf AND TIMESTAMP ".($tnfull?"<=":"<")." $tn ";
  6180. } else {
  6181. if ($dbmodel eq "POSTGRESQL") {
  6182. $sql .= "true ";
  6183. } else {
  6184. $sql .= "1 ";
  6185. }
  6186. }
  6187. $sql .= "$addon;";
  6188. return $sql;
  6189. }
  6190. ####################################################################################################
  6191. # SQL-Statement zusammenstellen für Löschvorgänge
  6192. ####################################################################################################
  6193. sub DbRep_createDeleteSql($$$$$$$) {
  6194. my ($hash,$table,$device,$reading,$tf,$tn,$addon) = @_;
  6195. my $name = $hash->{NAME};
  6196. my $dbmodel = $hash->{dbloghash}{MODEL};
  6197. my ($sql,$devs,$danz,$ranz);
  6198. my $tnfull = 0;
  6199. if($table eq "current") {
  6200. $sql = "delete FROM $table; ";
  6201. return $sql;
  6202. }
  6203. ($devs,$danz,$reading,$ranz) = DbRep_specsForSql($hash,$device,$reading);
  6204. if($tn =~ /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
  6205. $tnfull = 1;
  6206. }
  6207. $sql = "delete FROM $table where ";
  6208. $sql .= "DEVICE LIKE '$devs' AND " if($danz <= 1 && $devs !~ m(^%$) && $devs =~ m(\%));
  6209. $sql .= "DEVICE = '$devs' AND " if($danz <= 1 && $devs !~ m(\%));
  6210. $sql .= "DEVICE IN ($devs) AND " if($danz > 1);
  6211. $sql .= "READING LIKE '$reading' AND " if($ranz <= 1 && $reading !~ m(^%$) && $reading =~ m(\%));
  6212. $sql .= "READING = '$reading' AND " if($ranz <= 1 && $reading !~ m(\%));
  6213. $sql .= "READING IN ($reading) AND " if($ranz > 1);
  6214. if ($tf && $tn) {
  6215. $sql .= "TIMESTAMP >= '$tf' AND TIMESTAMP ".($tnfull?"<=":"<")." '$tn' $addon;";
  6216. } else {
  6217. if ($dbmodel eq "POSTGRESQL") {
  6218. $sql .= "true;";
  6219. } else {
  6220. $sql .= "1;";
  6221. }
  6222. }
  6223. return $sql;
  6224. }
  6225. ####################################################################################################
  6226. # Ableiten von Device, Reading-Spezifikationen
  6227. ####################################################################################################
  6228. sub DbRep_specsForSql($$$) {
  6229. my ($hash,$device,$reading) = @_;
  6230. my $name = $hash->{NAME};
  6231. my @dvspcs = devspec2array($device);
  6232. my $devs = join(",",@dvspcs);
  6233. $devs =~ s/'/''/g; # escape ' with ''
  6234. my $danz = $#dvspcs+1;
  6235. if ($danz > 1) {
  6236. $devs =~ s/,/','/g;
  6237. $devs = "'".$devs."'";
  6238. }
  6239. Log3 $name, 5, "DbRep $name - Device specifications use for select: $devs";
  6240. $reading =~ s/'/''/g; # escape ' with ''
  6241. my @reads = split(",",$reading);
  6242. my $ranz = $#reads+1;
  6243. if ($ranz > 1) {
  6244. $reading =~ s/,/','/g;
  6245. $reading = "'".$reading."'";
  6246. }
  6247. Log3 $name, 5, "DbRep $name - Reading specification use for select: $reading";
  6248. return ($devs,$danz,$reading,$ranz);
  6249. }
  6250. ####################################################################################################
  6251. # Check ob Zeitgrenzen bzw. Aggregation gesetzt sind, evtl. übertseuern (je nach Funktion)
  6252. # Return "1" wenn Bedingung erfüllt, sonst "0"
  6253. ####################################################################################################
  6254. sub DbRep_checktimeaggr ($) {
  6255. my ($hash) = @_;
  6256. my $name = $hash->{NAME};
  6257. my $IsTimeSet = 0;
  6258. my $IsAggrSet = 0;
  6259. my $aggregation = AttrVal($name,"aggregation","no");
  6260. if ( AttrVal($name,"timestamp_begin",undef) || AttrVal($name,"timestamp_end",undef) ||
  6261. AttrVal($name,"timeDiffToNow",undef) || AttrVal($name,"timeOlderThan",undef) || AttrVal($name,"timeYearPeriod",undef) ) {
  6262. $IsTimeSet = 1;
  6263. }
  6264. if ($aggregation ne "no") {
  6265. $IsAggrSet = 1;
  6266. }
  6267. if($hash->{LASTCMD} =~ /delSeqDoublets/) {
  6268. $aggregation = ($aggregation eq "no")?"day":$aggregation; # wenn Aggregation "no", für delSeqDoublets immer "day" setzen
  6269. $IsAggrSet = 1;
  6270. }
  6271. if($hash->{LASTCMD} =~ /averageValue/ && AttrVal($name,"averageCalcForm","avgArithmeticMean") eq "avgDailyMeanGWS") {
  6272. $aggregation = "day"; # für Tagesmittelwertberechnung des deutschen Wetterdienstes immer "day"
  6273. $IsAggrSet = 1;
  6274. }
  6275. if($hash->{LASTCMD} =~ /delEntries|fetchrows|deviceRename|readingRename|tableCurrentFillup/) {
  6276. $IsAggrSet = 0;
  6277. $aggregation = "no";
  6278. }
  6279. if($hash->{LASTCMD} =~ /deviceRename|readingRename/) {
  6280. $IsTimeSet = 0;
  6281. }
  6282. if($hash->{LASTCMD} =~ /changeValue/) {
  6283. if($hash->{HELPER}{COMPLEX}) {
  6284. $IsAggrSet = 1;
  6285. $aggregation = "day";
  6286. } else {
  6287. $IsAggrSet = 0;
  6288. $aggregation = "no";
  6289. }
  6290. }
  6291. if($hash->{LASTCMD} =~ /syncStandby/ ) {
  6292. if($aggregation !~ /day|hour|week/) {
  6293. $aggregation = "day";
  6294. $IsAggrSet = 1;
  6295. }
  6296. }
  6297. return ($IsTimeSet,$IsAggrSet,$aggregation);
  6298. }
  6299. ####################################################################################################
  6300. # ReadingsSingleUpdate für Reading, Value, Event
  6301. ####################################################################################################
  6302. sub ReadingsSingleUpdateValue ($$$$) {
  6303. my ($hash,$reading,$val,$ev) = @_;
  6304. my $name = $hash->{NAME};
  6305. readingsSingleUpdate($hash, $reading, $val, $ev);
  6306. DbRep_userexit($name, $reading, $val);
  6307. return;
  6308. }
  6309. ####################################################################################################
  6310. # Readingsbulkupdate für Reading, Value
  6311. # readingsBeginUpdate und readingsEndUpdate muss vor/nach Funktionsaufruf gesetzt werden
  6312. ####################################################################################################
  6313. sub ReadingsBulkUpdateValue ($$$) {
  6314. my ($hash,$reading,$val) = @_;
  6315. my $name = $hash->{NAME};
  6316. readingsBulkUpdate($hash, $reading, $val);
  6317. DbRep_userexit($name, $reading, $val);
  6318. return;
  6319. }
  6320. ####################################################################################################
  6321. # Readingsbulkupdate für processing_time, state
  6322. # readingsBeginUpdate und readingsEndUpdate muss vor/nach Funktionsaufruf gesetzt werden
  6323. ####################################################################################################
  6324. sub ReadingsBulkUpdateTimeState ($$$$) {
  6325. my ($hash,$brt,$rt,$sval) = @_;
  6326. my $name = $hash->{NAME};
  6327. if(AttrVal($name, "showproctime", undef)) {
  6328. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt)) if(defined($brt));
  6329. DbRep_userexit($name, "background_processing_time", sprintf("%.4f",$brt)) if(defined($brt));
  6330. readingsBulkUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt)) if(defined($rt));
  6331. DbRep_userexit($name, "sql_processing_time", sprintf("%.4f",$rt)) if(defined($rt));
  6332. }
  6333. readingsBulkUpdate($hash, "state", $sval);
  6334. DbRep_userexit($name, "state", $sval);
  6335. return;
  6336. }
  6337. ####################################################################################################
  6338. # Anzeige von laufenden Blocking Prozessen
  6339. ####################################################################################################
  6340. sub DbRep_getblockinginfo($@) {
  6341. my ($hash) = @_;
  6342. my $name = $hash->{NAME};
  6343. my @rows;
  6344. our %BC_hash;
  6345. my $len = 99;
  6346. foreach my $h (values %BC_hash) {
  6347. next if($h->{terminated} || !$h->{pid});
  6348. my @allk = keys%{$h};
  6349. foreach my $k (@allk) {
  6350. Log3 ($name, 5, "DbRep $name -> $k : ".$h->{$k});
  6351. }
  6352. my $fn = (ref($h->{fn}) ? ref($h->{fn}) : $h->{fn});
  6353. my $arg = (ref($h->{arg}) ? ref($h->{arg}) : $h->{arg});
  6354. my $arg1 = substr($arg,0,$len);
  6355. $arg1 = $arg1."..." if(length($arg) > $len+1);
  6356. my $to = ($h->{timeout} ? $h->{timeout} : "N/A");
  6357. my $conn = ($h->{telnet} ? $h->{telnet} : "N/A");
  6358. push @rows, "$h->{pid}|ESCAPED|$fn|ESCAPED|$arg1|ESCAPED|$to|ESCAPED|$conn";
  6359. }
  6360. # Readingaufbereitung
  6361. readingsBeginUpdate($hash);
  6362. if(!@rows) {
  6363. ReadingsBulkUpdateTimeState($hash,undef,undef,"done - No BlockingCall processes running");
  6364. readingsEndUpdate($hash, 1);
  6365. return;
  6366. }
  6367. my $res = "<html><table border=2 bordercolor='darkgreen' cellspacing=0>";
  6368. $res .= "<tr><td style='padding-right:5px;padding-left:5px;font-weight:bold'>PID</td>";
  6369. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>FUNCTION</td>";
  6370. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>ARGUMENTS</td>";
  6371. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>TIMEOUT</td>";
  6372. $res .= "<td style='padding-right:5px;padding-left:5px;font-weight:bold'>CONNECTEDVIA</td></tr>";
  6373. foreach my $row (@rows) {
  6374. $row =~ s/\|ESCAPED\|/<\/td><td style='padding-right:5px;padding-left:5px'>/g;
  6375. $res .= "<tr><td style='padding-right:5px;padding-left:5px'>".$row."</td></tr>";
  6376. }
  6377. my $tab = $res."</table></html>";
  6378. ReadingsBulkUpdateValue ($hash,"BlockingInfo",$tab);
  6379. ReadingsBulkUpdateValue ($hash,"Blocking_Count",$#rows+1);
  6380. ReadingsBulkUpdateTimeState($hash,undef,undef,"done");
  6381. readingsEndUpdate($hash, 1);
  6382. return;
  6383. }
  6384. ####################################################################################################
  6385. # relative Zeitangaben als Sekunden normieren
  6386. #
  6387. # liefert die Attribute timeOlderThan, timeDiffToNow als Sekunden normiert zurück
  6388. ####################################################################################################
  6389. sub DbRep_normRelTime ($) {
  6390. my ($hash) = @_;
  6391. my $name = $hash->{NAME};
  6392. my $tdtn = AttrVal($name, "timeDiffToNow", undef);
  6393. my $toth = AttrVal($name, "timeOlderThan", undef);
  6394. if($tdtn && $tdtn =~ /^\s*[dhms]:(([\d]+.[\d]+)|[\d]+)\s*/) {
  6395. my ($d,$h,$m,$s);
  6396. if($tdtn =~ /.*d:(([\d]+.[\d]+)|[\d]+).*/) {
  6397. $d = $tdtn;
  6398. $d =~ s/.*d:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6399. }
  6400. if($tdtn =~ /.*h:(([\d]+.[\d]+)|[\d]+).*/) {
  6401. $h = $tdtn;
  6402. $h =~ s/.*h:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6403. }
  6404. if($tdtn =~ /.*m:(([\d]+.[\d]+)|[\d]+).*/) {
  6405. $m = $tdtn;
  6406. $m =~ s/.*m:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6407. }
  6408. if($tdtn =~ /.*s:(([\d]+.[\d]+)|[\d]+).*/) {
  6409. $s = $tdtn;
  6410. $s =~ s/.*s:(([\d]+.[\d]+)|[\d]+).*/$1/e ;
  6411. }
  6412. no warnings 'uninitialized';
  6413. Log3($name, 4, "DbRep $name - timeDiffToNow - day: $d, hour: $h, min: $m, sec: $s ");
  6414. use warnings;
  6415. $d = $d?($d*86400)+1:0; # one security second for correct create TimeArray
  6416. $h = $h?($h*3600)+1:0;
  6417. $m = $m?($m*60)+1:0;
  6418. $s = $s?$s:0;
  6419. $tdtn = $d + $h + $m +$s;
  6420. }
  6421. if($toth && $toth =~ /^\s*[dhms]:(([\d]+.[\d]+)|[\d]+)\s*/) {
  6422. my ($d,$h,$m,$s);
  6423. if($toth =~ /.*d:(([\d]+.[\d]+)|[\d]+).*/) {
  6424. $d = $toth;
  6425. $d =~ s/.*d:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6426. }
  6427. if($toth =~ /.*h:(([\d]+.[\d]+)|[\d]+).*/) {
  6428. $h = $toth;
  6429. $h =~ s/.*h:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6430. }
  6431. if($toth =~ /.*m:(([\d]+.[\d]+)|[\d]+).*/) {
  6432. $m = $toth;
  6433. $m =~ s/.*m:(([\d]+.[\d]+)|[\d]+).*/$1/e;
  6434. }
  6435. if($toth =~ /.*s:(([\d]+.[\d]+)|[\d]+).*/) {
  6436. $s = $toth;
  6437. $s =~ s/.*s:(([\d]+.[\d]+)|[\d]+).*/$1/e ;
  6438. }
  6439. no warnings 'uninitialized';
  6440. Log3($name, 4, "DbRep $name - timeDiffToNow - day: $d, hour: $h, min: $m, sec: $s ");
  6441. use warnings;
  6442. $d = $d?($d*86400)+1:0; # one security second for correct create TimeArray
  6443. $h = $h?($h*3600)+1:0;
  6444. $m = $m?($m*60)+1:0;
  6445. $s = $s?$s:0;
  6446. $toth = $d + $h + $m +$s;
  6447. }
  6448. return ($toth,$tdtn);
  6449. }
  6450. ###############################################################################
  6451. # Zeichencodierung für Fileexport filtern
  6452. ###############################################################################
  6453. sub DbRep_charfilter ($) {
  6454. my ($txt) = @_;
  6455. # nur erwünschte Zeichen, Filtern von Steuerzeichen
  6456. $txt =~ tr/ A-Za-z0-9!"#$%&'()*+,-.\/:;<=>?@[\\]^_`{|}~äöüÄÖÜ߀//cd;
  6457. return($txt);
  6458. }
  6459. ###################################################################################
  6460. # Befehl vor Procedure ausführen
  6461. ###################################################################################
  6462. sub DbRep_beforeproc ($$) {
  6463. my ($hash, $txt) = @_;
  6464. my $name = $hash->{NAME};
  6465. # Befehl vor Procedure ausführen
  6466. my $ebd = AttrVal($name, "executeBeforeProc", undef);
  6467. if($ebd) {
  6468. Log3 ($name, 3, "DbRep $name - execute command before $txt: '$ebd' ");
  6469. my $err = AnalyzeCommandChain(undef, $ebd);
  6470. if ($err) {
  6471. Log3 ($name, 2, "DbRep $name - command before $txt message: \"$err\" ");
  6472. my $erread = "Warning - message from command before $txt appeared";
  6473. ReadingsSingleUpdateValue ($hash, "before".$txt."_message", $err, 1);
  6474. ReadingsSingleUpdateValue ($hash, "state", $erread, 1);
  6475. }
  6476. }
  6477. return;
  6478. }
  6479. ###################################################################################
  6480. # Befehl nach Procedure ausführen
  6481. ###################################################################################
  6482. sub DbRep_afterproc ($$) {
  6483. my ($hash, $txt) = @_;
  6484. my $name = $hash->{NAME};
  6485. my $erread;
  6486. # Befehl nach Procedure ausführen
  6487. no warnings 'uninitialized';
  6488. my $ead = AttrVal($name, "executeAfterProc", undef);
  6489. if($ead) {
  6490. Log3 ($name, 4, "DbRep $name - execute command after $txt: '$ead' ");
  6491. my $err = AnalyzeCommandChain(undef, $ead);
  6492. if ($err) {
  6493. Log3 ($name, 2, "DbRep $name - command after $txt message: \"$err\" ");
  6494. ReadingsSingleUpdateValue ($hash, "after".$txt."_message", $err, 1);
  6495. $erread = "Warning - $txt finished, but command after $txt message appeared";
  6496. }
  6497. }
  6498. return $erread;
  6499. }
  6500. ##############################################################################################
  6501. # timestamp_begin, timestamp_end bei Einsatz datetime-Picker entsprechend
  6502. # den Anforderungen formatieren
  6503. ##############################################################################################
  6504. sub DbRep_formatpicker ($) {
  6505. my ($str) = @_;
  6506. if ($str =~ /^(\d{4})-(\d{2})-(\d{2})_(\d{2}):(\d{2})$/) {
  6507. # Anpassung für datetime-Picker Widget
  6508. $str =~ s/_/ /;
  6509. $str = $str.":00";
  6510. }
  6511. if ($str =~ /^(\d{4})-(\d{2})-(\d{2})_(\d{2}):(\d{2}):(\d{2})$/) {
  6512. # Anpassung für datetime-Picker Widget
  6513. $str =~ s/_/ /;
  6514. }
  6515. return $str;
  6516. }
  6517. ####################################################################################################
  6518. # userexit - Funktion um userspezifische Programmaufrufe nach Aktualisierung eines Readings
  6519. # zu ermöglichen, arbeitet OHNE Event abhängig vom Attr userExitFn
  6520. #
  6521. # Aufruf der <UserExitFn> mit $name,$reading,$value
  6522. ####################################################################################################
  6523. sub DbRep_userexit ($$$) {
  6524. my ($name,$reading,$value) = @_;
  6525. my $hash = $defs{$name};
  6526. return if(!$hash->{HELPER}{USEREXITFN});
  6527. if(!defined($reading)) {$reading = "";}
  6528. if(!defined($value)) {$value = "";}
  6529. $value =~ s/\\/\\\\/g; # escapen of chars for evaluation
  6530. $value =~ s/'/\\'/g;
  6531. my $re = $hash->{HELPER}{UEFN_REGEXP}?$hash->{HELPER}{UEFN_REGEXP}:".*:.*";
  6532. if("$reading:$value" =~ m/^$re$/ ) {
  6533. my @res;
  6534. my $cmd = $hash->{HELPER}{USEREXITFN}."('$name','$reading','$value')";
  6535. $cmd = "{".$cmd."}";
  6536. my $r = AnalyzeCommandChain(undef, $cmd);
  6537. }
  6538. return;
  6539. }
  6540. ####################################################################################################
  6541. # delete Readings before new operation
  6542. ####################################################################################################
  6543. sub DbRep_delread($;$$) {
  6544. # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen
  6545. my ($hash,$shutdown) = @_;
  6546. my $name = $hash->{NAME};
  6547. my @allrds = keys%{$defs{$name}{READINGS}};
  6548. if($shutdown) {
  6549. my $do = 0;
  6550. foreach my $key(@allrds) {
  6551. # Highlighted Readings löschen und save statefile wegen Inkompatibilitär beim Restart
  6552. if($key =~ /<html><span/) {
  6553. $do = 1;
  6554. delete($defs{$name}{READINGS}{$key});
  6555. }
  6556. }
  6557. WriteStatefile() if($do == 1);
  6558. return undef;
  6559. }
  6560. my @rdpfdel = split(",", $hash->{HELPER}{RDPFDEL}) if($hash->{HELPER}{RDPFDEL});
  6561. if(@rdpfdel) {
  6562. foreach my $key(@allrds) {
  6563. # Log3 ($name, 1, "DbRep $name - Reading Schlüssel: $key");
  6564. my $dodel = 1;
  6565. foreach my $rdpfdel(@rdpfdel) {
  6566. if($key =~ /$rdpfdel/ || $key eq "state") {
  6567. $dodel = 0;
  6568. }
  6569. }
  6570. if($dodel) {
  6571. delete($defs{$name}{READINGS}{$key});
  6572. }
  6573. }
  6574. } else {
  6575. foreach my $key(@allrds) {
  6576. # Log3 ($name, 1, "DbRep $name - Reading Schlüssel: $key");
  6577. delete($defs{$name}{READINGS}{$key}) if($key ne "state");
  6578. }
  6579. }
  6580. return undef;
  6581. }
  6582. ####################################################################################################
  6583. # erstellen neues SQL-File für Dumproutine
  6584. ####################################################################################################
  6585. sub DbRep_NewDumpFilename ($$$$$){
  6586. my ($sql_text,$dump_path,$dbname,$time_stamp,$character_set) = @_;
  6587. my $part = "";
  6588. my $sql_file = $dump_path.$dbname."_".$time_stamp.$part.".sql";
  6589. my $backupfile = $dbname."_".$time_stamp.$part.".sql";
  6590. $sql_text .= "/*!40101 SET NAMES '".$character_set."' */;\n";
  6591. $sql_text .= "SET FOREIGN_KEY_CHECKS=0;\n";
  6592. my ($filesize,$err) = DbRep_WriteToDumpFile($sql_text,$sql_file);
  6593. if($err) {
  6594. return (undef,undef,undef,undef,$err);
  6595. }
  6596. chmod(0777,$sql_file);
  6597. $sql_text = "";
  6598. my $first_insert = 0;
  6599. return ($sql_text,$first_insert,$sql_file,$backupfile,undef);
  6600. }
  6601. ####################################################################################################
  6602. # Schreiben DB-Dumps in SQL-File
  6603. ####################################################################################################
  6604. sub DbRep_WriteToDumpFile ($$) {
  6605. my ($inh,$sql_file) = @_;
  6606. my $filesize;
  6607. my $err = 0;
  6608. if(length($inh) > 0) {
  6609. unless(open(DATEI,">>$sql_file")) {
  6610. $err = "Can't open file '$sql_file' for write access";
  6611. return (undef,$err);
  6612. }
  6613. print DATEI $inh;
  6614. close(DATEI);
  6615. $filesize = (stat($sql_file))[7];
  6616. }
  6617. return ($filesize,undef);
  6618. }
  6619. ####################################################################################################
  6620. # Filesize (Byte) umwandeln in KB bzw. MB
  6621. ####################################################################################################
  6622. sub DbRep_byteOutput ($) {
  6623. my $bytes = shift;
  6624. return if(!defined($bytes));
  6625. return $bytes if(!looks_like_number($bytes));
  6626. my $suffix = "Bytes";
  6627. if ($bytes >= 1024) { $suffix = "KB"; $bytes = sprintf("%.2f",($bytes/1024));};
  6628. if ($bytes >= 1024) { $suffix = "MB"; $bytes = sprintf("%.2f",($bytes/1024));};
  6629. my $ret = sprintf "%.2f",$bytes;
  6630. $ret.=' '.$suffix;
  6631. return $ret;
  6632. }
  6633. ####################################################################################################
  6634. # Schreibroutine in DbRep Keyvalue-File
  6635. ####################################################################################################
  6636. sub DbRep_setCmdFile($$$) {
  6637. my ($key,$value,$hash) = @_;
  6638. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheDbRep";
  6639. my $param = {
  6640. FileName => $fName,
  6641. ForceType => "file",
  6642. };
  6643. my ($err, @old) = FileRead($param);
  6644. DbRep_createCmdFile($hash) if($err);
  6645. my @new;
  6646. my $fnd;
  6647. foreach my $l (@old) {
  6648. if($l =~ m/^$key:/) {
  6649. $fnd = 1;
  6650. push @new, "$key:$value" if(defined($value));
  6651. } else {
  6652. push @new, $l;
  6653. }
  6654. }
  6655. push @new, "$key:$value" if(!$fnd && defined($value));
  6656. return FileWrite($param, @new);
  6657. }
  6658. ####################################################################################################
  6659. # anlegen Keyvalue-File für DbRep wenn nicht vorhanden
  6660. ####################################################################################################
  6661. sub DbRep_createCmdFile ($) {
  6662. my ($hash) = @_;
  6663. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheDbRep";
  6664. my $param = {
  6665. FileName => $fName,
  6666. ForceType => "file",
  6667. };
  6668. my @new;
  6669. push(@new, "# This file is auto generated from 93_DbRep.",
  6670. "# Please do not modify, move or delete it.",
  6671. "");
  6672. return FileWrite($param, @new);
  6673. }
  6674. ####################################################################################################
  6675. # Leseroutine aus DbRep Keyvalue-File
  6676. ####################################################################################################
  6677. sub DbRep_getCmdFile($) {
  6678. my ($key) = @_;
  6679. my $fName = $attr{global}{modpath}."/FHEM/FhemUtils/cacheDbRep";
  6680. my $param = {
  6681. FileName => $fName,
  6682. ForceType => "file",
  6683. };
  6684. my ($err, @l) = FileRead($param);
  6685. return ($err, undef) if($err);
  6686. for my $l (@l) {
  6687. return (undef, $1) if($l =~ m/^$key:(.*)/);
  6688. }
  6689. return (undef, undef);
  6690. }
  6691. ####################################################################################################
  6692. # Tabellenoptimierung MySQL
  6693. ####################################################################################################
  6694. sub DbRep_mysqlOptimizeTables ($$@) {
  6695. my ($hash,$dbh,@tablenames) = @_;
  6696. my $name = $hash->{NAME};
  6697. my $dbname = $hash->{DATABASE};
  6698. my $ret = 0;
  6699. my $opttbl = 0;
  6700. my $db_tables = $hash->{HELPER}{DBTABLES};
  6701. my ($engine,$tablename,$query,$sth,$value,$db_MB_start,$db_MB_end);
  6702. # Anfangsgröße ermitteln
  6703. $query = "SELECT sum( data_length + index_length ) / 1024 / 1024 FROM information_schema.TABLES where table_schema='$dbname' ";
  6704. Log3 ($name, 5, "DbRep $name - current query: $query ");
  6705. eval { $sth = $dbh->prepare($query);
  6706. $sth->execute;
  6707. };
  6708. if ($@) {
  6709. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  6710. $sth->finish;
  6711. $dbh->disconnect;
  6712. return ($@,undef,undef);
  6713. }
  6714. $value = $sth->fetchrow();
  6715. $db_MB_start = sprintf("%.2f",$value);
  6716. Log3 ($name, 3, "DbRep $name - Size of database $dbname before optimize (MB): $db_MB_start");
  6717. Log3($name, 3, "DbRep $name - Optimizing tables");
  6718. foreach $tablename (@tablenames) {
  6719. #optimize table if engine supports optimization
  6720. $engine = '';
  6721. $engine = uc($db_tables->{$tablename}{Engine}) if($db_tables->{$tablename}{Engine});
  6722. if ($engine =~ /(MYISAM|BDB|INNODB|ARIA)/) {
  6723. Log3($name, 3, "DbRep $name - Optimizing table `$tablename` ($engine). It will take a while.");
  6724. my $sth_to = $dbh->prepare("OPTIMIZE TABLE `$tablename`");
  6725. $ret = $sth_to->execute;
  6726. if ($ret) {
  6727. Log3($name, 3, "DbRep $name - Table ".($opttbl+1)." `$tablename` optimized successfully.");
  6728. $opttbl++;
  6729. } else {
  6730. Log3($name, 2, "DbRep $name - Error while optimizing table $tablename. Continue with next table or backup.");
  6731. }
  6732. }
  6733. }
  6734. Log3($name, 3, "DbRep $name - $opttbl tables have been optimized.") if($opttbl > 0);
  6735. # Endgröße ermitteln
  6736. eval { $sth->execute; };
  6737. if ($@) {
  6738. Log3 ($name, 2, "DbRep $name - Error executing: '".$query."' ! MySQL-Error: ".$@);
  6739. $sth->finish;
  6740. $dbh->disconnect;
  6741. return ($@,undef,undef);
  6742. }
  6743. $value = $sth->fetchrow();
  6744. $db_MB_end = sprintf("%.2f",$value);
  6745. Log3 ($name, 3, "DbRep $name - Size of database $dbname after optimize (MB): $db_MB_end");
  6746. $sth->finish;
  6747. return (undef,$db_MB_start,$db_MB_end);
  6748. }
  6749. ####################################################################################################
  6750. # Dump-Files im dumpDirLocal löschen bis auf die letzten "n"
  6751. ####################################################################################################
  6752. sub DbRep_deldumpfiles ($$) {
  6753. my ($hash,$bfile) = @_;
  6754. my $name = $hash->{NAME};
  6755. my $dbloghash = $hash->{dbloghash};
  6756. my $dump_path_def = $attr{global}{modpath}."/log/";
  6757. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  6758. my $dfk = AttrVal($name,"dumpFilesKeep", 3);
  6759. my $pfix = (split '\.', $bfile)[1];
  6760. my $dbname = (split '_', $bfile)[0];
  6761. my $file = $dbname."_.*".$pfix.".*"; # Files mit/ohne Endung "gzip" berücksichtigen
  6762. my @fd;
  6763. if(!opendir(DH, $dump_path_loc)) {
  6764. push(@fd, "No files deleted - Can't open path '$dump_path_loc'");
  6765. return @fd;
  6766. }
  6767. my @files = sort grep {/^$file$/} readdir(DH);
  6768. @files = sort { (stat("$dump_path_loc/$a"))[9] cmp (stat("$dump_path_loc/$b"))[9] } @files
  6769. if(AttrVal("global", "archivesort", "alphanum") eq "timestamp");
  6770. closedir(DH);
  6771. Log3($name, 5, "DbRep $name - Dump files have been found in dumpDirLocal '$dump_path_loc': ".join(', ',@files) );
  6772. my $max = int(@files)-$dfk;
  6773. for(my $i = 0; $i < $max; $i++) {
  6774. push(@fd, $files[$i]);
  6775. Log3($name, 3, "DbRep $name - Deleting old dumpfile '$files[$i]' ");
  6776. unlink("$dump_path_loc/$files[$i]");
  6777. }
  6778. return @fd;
  6779. }
  6780. ####################################################################################################
  6781. # Dumpfile komprimieren
  6782. ####################################################################################################
  6783. sub DbRep_dumpCompress ($$) {
  6784. my ($hash,$bfile) = @_;
  6785. my $name = $hash->{NAME};
  6786. my $dump_path_def = $attr{global}{modpath}."/log/";
  6787. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  6788. $dump_path_loc =~ s/(\/$|\\$)//;
  6789. my $input = $dump_path_loc."/".$bfile;
  6790. my $output = $dump_path_loc."/".$bfile.".gzip";
  6791. Log3($name, 3, "DbRep $name - compress file $input");
  6792. my $stat = gzip $input => $output ,BinModeIn => 1;
  6793. if($GzipError) {
  6794. Log3($name, 2, "DbRep $name - gzip of $input failed: $GzipError");
  6795. return ($GzipError,$input);
  6796. }
  6797. Log3($name, 3, "DbRep $name - file compressed to output file: $output");
  6798. unlink("$input");
  6799. Log3($name, 3, "DbRep $name - input file deleted: $input");
  6800. return (undef,$bfile.".gzip");
  6801. }
  6802. ####################################################################################################
  6803. # Dumpfile dekomprimieren
  6804. ####################################################################################################
  6805. sub DbRep_dumpUnCompress ($$) {
  6806. my ($hash,$bfile) = @_;
  6807. my $name = $hash->{NAME};
  6808. my $dump_path_def = $attr{global}{modpath}."/log/";
  6809. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  6810. $dump_path_loc =~ s/(\/$|\\$)//;
  6811. my $input = $dump_path_loc."/".$bfile;
  6812. my $outfile = $bfile;
  6813. $outfile =~ s/\.gzip//;
  6814. my $output = $dump_path_loc."/".$outfile;
  6815. Log3($name, 3, "DbRep $name - uncompress file $input");
  6816. my $stat = gunzip $input => $output ,BinModeOut => 1;
  6817. if($GunzipError) {
  6818. Log3($name, 2, "DbRep $name - gunzip of $input failed: $GunzipError");
  6819. return ($GunzipError,$input);
  6820. }
  6821. Log3($name, 3, "DbRep $name - file uncompressed to output file: $output");
  6822. # Größe dekomprimiertes File ermitteln
  6823. my @a = split(' ',qx(du $output)) if ($^O =~ m/linux/i || $^O =~ m/unix/i);
  6824. my $filesize = ($a[0])?($a[0]*1024):undef;
  6825. my $fsize = DbRep_byteOutput($filesize);
  6826. Log3 ($name, 3, "DbRep $name - Size of uncompressed file: ".$fsize);
  6827. return (undef,$outfile);
  6828. }
  6829. ####################################################################################################
  6830. # erzeugtes Dump-File aus dumpDirLocal zum FTP-Server übertragen
  6831. ####################################################################################################
  6832. sub DbRep_sendftp ($$) {
  6833. my ($hash,$bfile) = @_;
  6834. my $name = $hash->{NAME};
  6835. my $dump_path_def = $attr{global}{modpath}."/log/";
  6836. my $dump_path_loc = AttrVal($name,"dumpDirLocal", $dump_path_def);
  6837. my $file = (split /[\/]/, $bfile)[-1];
  6838. my $ftpto = AttrVal($name,"ftpTimeout",30);
  6839. my $ftpUse = AttrVal($name,"ftpUse",0);
  6840. my $ftpuseSSL = AttrVal($name,"ftpUseSSL",0);
  6841. my $ftpDir = AttrVal($name,"ftpDir","/");
  6842. my $ftpPort = AttrVal($name,"ftpPort",21);
  6843. my $ftpServer = AttrVal($name,"ftpServer",undef);
  6844. my $ftpUser = AttrVal($name,"ftpUser","anonymous");
  6845. my $ftpPwd = AttrVal($name,"ftpPwd",undef);
  6846. my $ftpPassive = AttrVal($name,"ftpPassive",0);
  6847. my $ftpDebug = AttrVal($name,"ftpDebug",0);
  6848. my $fdfk = AttrVal($name,"ftpDumpFilesKeep", 3);
  6849. my $pfix = (split '\.', $bfile)[1];
  6850. my $dbname = (split '_', $bfile)[0];
  6851. my $ftpl = $dbname."_.*".$pfix.".*"; # Files mit/ohne Endung "gzip" berücksichtigen
  6852. my ($ftperr,$ftpmsg,$ftp);
  6853. # kein FTP verwenden oder möglich
  6854. return ($ftperr,$ftpmsg) if((!$ftpUse && !$ftpuseSSL) || !$bfile);
  6855. if(!$ftpServer) {
  6856. $ftperr = "FTP-Error: FTP-Server isn't set.";
  6857. Log3($name, 2, "DbRep $name - $ftperr");
  6858. return ($ftperr,undef);
  6859. }
  6860. if(!opendir(DH, $dump_path_loc)) {
  6861. $ftperr = "FTP-Error: Can't open path '$dump_path_loc'";
  6862. Log3($name, 2, "DbRep $name - $ftperr");
  6863. return ($ftperr,undef);
  6864. }
  6865. my $mod_ftpssl = 0;
  6866. my $mod_ftp = 0;
  6867. my $mod;
  6868. if ($ftpuseSSL) {
  6869. # FTP mit SSL soll genutzt werden
  6870. $mod = "Net::FTPSSL => e.g. with 'sudo cpan -i Net::FTPSSL' ";
  6871. eval { require Net::FTPSSL; };
  6872. if(!$@){
  6873. $mod_ftpssl = 1;
  6874. import Net::FTPSSL;
  6875. }
  6876. } else {
  6877. # nur FTP
  6878. $mod = "Net::FTP";
  6879. eval { require Net::FTP; };
  6880. if(!$@){
  6881. $mod_ftp = 1;
  6882. import Net::FTP;
  6883. }
  6884. }
  6885. if ($ftpuseSSL && $mod_ftpssl) {
  6886. # use ftp-ssl
  6887. my $enc = "E";
  6888. eval { $ftp = Net::FTPSSL->new($ftpServer, Port => $ftpPort, Timeout => $ftpto, Debug => $ftpDebug, Encryption => $enc) }
  6889. or $ftperr = "FTP-SSL-ERROR: Can't connect - $@";
  6890. } elsif (!$ftpuseSSL && $mod_ftp) {
  6891. # use plain ftp
  6892. eval { $ftp = Net::FTP->new($ftpServer, Port => $ftpPort, Timeout => $ftpto, Debug => $ftpDebug, Passive => $ftpPassive) }
  6893. or $ftperr = "FTP-Error: Can't connect - $@";
  6894. } else {
  6895. $ftperr = "FTP-Error: required module couldn't be loaded. You have to install it first: $mod.";
  6896. }
  6897. if ($ftperr) {
  6898. Log3($name, 2, "DbRep $name - $ftperr");
  6899. return ($ftperr,undef);
  6900. }
  6901. my $pwdstr = $ftpPwd?$ftpPwd:" ";
  6902. $ftp->login($ftpUser, $ftpPwd) or $ftperr = "FTP-Error: Couldn't login with user '$ftpUser' and password '$pwdstr' ";
  6903. if ($ftperr) {
  6904. Log3($name, 2, "DbRep $name - $ftperr");
  6905. return ($ftperr,undef);
  6906. }
  6907. $ftp->binary();
  6908. # FTP Verzeichnis setzen
  6909. $ftp->cwd($ftpDir) or $ftperr = "FTP-Error: Couldn't change directory to '$ftpDir' ";
  6910. if ($ftperr) {
  6911. Log3($name, 2, "DbRep $name - $ftperr");
  6912. return ($ftperr,undef);
  6913. }
  6914. $dump_path_loc =~ s/(\/$|\\$)//;
  6915. Log3($name, 3, "DbRep $name - FTP: transferring ".$dump_path_loc."/".$file);
  6916. $ftpmsg = $ftp->put($dump_path_loc."/".$file);
  6917. if (!$ftpmsg) {
  6918. $ftperr = "FTP-Error: Couldn't transfer ".$file." to ".$ftpServer." into dir ".$ftpDir;
  6919. Log3($name, 2, "DbRep $name - $ftperr");
  6920. } else {
  6921. $ftpmsg = "FTP: ".$file." transferred successfully to ".$ftpServer." into dir ".$ftpDir;
  6922. Log3($name, 3, "DbRep $name - $ftpmsg");
  6923. }
  6924. # Versionsverwaltung FTP-Verzeichnis
  6925. my (@ftl,@ftpfd);
  6926. if($ftpuseSSL) {
  6927. @ftl = sort grep {/^$ftpl$/} $ftp->nlst();
  6928. } else {
  6929. @ftl = sort grep {/^$ftpl$/} @{$ftp->ls()};
  6930. }
  6931. Log3($name, 5, "DbRep $name - FTP: filelist of \"$ftpDir\": @ftl");
  6932. my $max = int(@ftl)-$fdfk;
  6933. for(my $i = 0; $i < $max; $i++) {
  6934. push(@ftpfd, $ftl[$i]);
  6935. Log3($name, 3, "DbRep $name - FTP: deleting old dumpfile '$ftl[$i]' ");
  6936. $ftp->delete($ftl[$i]);
  6937. }
  6938. return ($ftperr,$ftpmsg,@ftpfd);
  6939. }
  6940. ####################################################################################################
  6941. # Test auf Daylight saving time
  6942. ####################################################################################################
  6943. sub DbRep_dsttest ($$$) {
  6944. my ($hash,$runtime,$aggsec) = @_;
  6945. my $name = $hash->{NAME};
  6946. my $dstchange = 0;
  6947. # der Wechsel der daylight saving time wird dadurch getestet, dass geprüft wird
  6948. # ob im Vergleich der aktuellen zur nächsten Selektionsperiode von "$aggsec (day, week, month)"
  6949. # ein Wechsel der daylight saving time vorliegt
  6950. my $dst = (localtime($runtime))[8]; # ermitteln daylight saving aktuelle runtime
  6951. my $time_str = localtime($runtime+$aggsec); # textual time representation
  6952. my $dst_new = (localtime($runtime+$aggsec))[8]; # ermitteln daylight saving nächste runtime
  6953. if ($dst != $dst_new) {
  6954. $dstchange = 1;
  6955. }
  6956. Log3 ($name, 5, "DbRep $name - Daylight savings changed: $dstchange (on $time_str)");
  6957. return $dstchange;
  6958. }
  6959. ####################################################################################################
  6960. # Counthash Untersuchung
  6961. # Logausgabe der Anzahl verarbeiteter Datensätze pro Zeitraum / Aggregation
  6962. # Rückgabe eines ncp-hash (no calc in period) mit den Perioden für die keine Differenz berechnet
  6963. # werden konnte weil nur ein Datensatz in der Periode zur Verfügung stand
  6964. ####################################################################################################
  6965. sub DbRep_calcount ($$) {
  6966. my ($hash,$ch) = @_;
  6967. my $name = $hash->{NAME};
  6968. my %ncp = ();
  6969. Log3 ($name, 4, "DbRep $name - count of values used for calc:");
  6970. foreach my $key (sort(keys%{$ch})) {
  6971. Log3 ($name, 4, "$key => ". $ch->{$key});
  6972. if($ch->{$key} eq "1") {
  6973. $ncp{"$key"} = " ||";
  6974. }
  6975. }
  6976. return \%ncp;
  6977. }
  6978. ####################################################################################################
  6979. # Funktionsergebnisse in Datenbank schreiben
  6980. ####################################################################################################
  6981. sub DbRep_OutputWriteToDB($$$$$) {
  6982. my ($name,$device,$reading,$arrstr,$optxt) = @_;
  6983. my $hash = $defs{$name};
  6984. my $dbloghash = $hash->{dbloghash};
  6985. my $dbconn = $dbloghash->{dbconn};
  6986. my $dbuser = $dbloghash->{dbuser};
  6987. my $dblogname = $dbloghash->{NAME};
  6988. my $dbmodel = $hash->{dbloghash}{MODEL};
  6989. my $DbLogType = AttrVal($hash->{dbloghash}{NAME}, "DbLogType", "History");
  6990. my $supk = AttrVal($hash->{dbloghash}{NAME}, "noSupportPK", 0);
  6991. my $dbpassword = $attr{"sec$dblogname"}{secret};
  6992. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  6993. $device =~ s/[^A-Za-z\/\d_\.-]/\//g;
  6994. $reading =~ s/[^A-Za-z\/\d_\.-]/\//g;
  6995. my $type = "calculated";
  6996. my $event = "calculated";
  6997. my $unit = "";
  6998. my $wrt = 0;
  6999. my $irowdone = 0;
  7000. my ($dbh,$sth_ih,$sth_uh,$sth_ic,$sth_uc,$err,$timestamp,$value,$date,$time,$rsf,$aggr,@row_array);
  7001. if(!$hash->{dbloghash}{HELPER}{COLSET}) {
  7002. $err = "No result of \"$hash->{LASTCMD}\" to database written. Cause: column width in \"$hash->{DEF}\" isn't set";
  7003. return ($wrt,$irowdone,$err);
  7004. }
  7005. no warnings 'uninitialized';
  7006. (undef,undef,$aggr) = DbRep_checktimeaggr($hash);
  7007. $reading = $optxt."_".$aggr."_".$reading;
  7008. $type = $defs{$device}{TYPE} if($defs{$device}); # $type vom Device ableiten
  7009. if($optxt =~ /avg|sum/) {
  7010. my @arr = split("\\|", $arrstr);
  7011. foreach my $row (@arr) {
  7012. my @a = split("#", $row);
  7013. my $runtime_string = $a[0]; # Aggregations-Alias (nicht benötigt)
  7014. $value = $a[1]?sprintf("%.4f",$a[1]):undef;
  7015. $rsf = $a[2]; # Datum / Zeit für DB-Speicherung
  7016. ($date,$time) = split("_",$rsf);
  7017. $time =~ s/-/:/g if($time);
  7018. if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/) {
  7019. if($aggr =~ /no|day|week|month/) {
  7020. $time = "23:59:58";
  7021. } elsif ($aggr =~ /hour/) {
  7022. $time = "$time:59:58";
  7023. }
  7024. }
  7025. if ($value) {
  7026. # Daten auf maximale Länge beschneiden (DbLog-Funktion !)
  7027. ($device,$type,$event,$reading,$value,$unit) = DbLog_cutCol($hash->{dbloghash},$device,$type,$event,$reading,$value,$unit);
  7028. push(@row_array, "$date $time|$device|$type|$event|$reading|$value|$unit");
  7029. }
  7030. }
  7031. }
  7032. if($optxt =~ /min|max|diff/) {
  7033. my %rh = split("§", $arrstr);
  7034. foreach my $key (sort(keys(%rh))) {
  7035. my @k = split("\\|",$rh{$key});
  7036. $rsf = $k[2]; # Datum / Zeit für DB-Speicherung
  7037. $value = $k[1]?sprintf("%.4f",$k[1]):undef;
  7038. ($date,$time) = split("_",$rsf);
  7039. $time =~ s/-/:/g if($time);
  7040. if($time !~ /^(\d{2}):(\d{2}):(\d{2})$/) {
  7041. if($aggr =~ /no|day|week|month/) {
  7042. $time = "23:59:58";
  7043. } elsif ($aggr =~ /hour/) {
  7044. $time = "$time:59:58";
  7045. }
  7046. }
  7047. if ($value) {
  7048. # Daten auf maximale Länge beschneiden (DbLog-Funktion !)
  7049. ($device,$type,$event,$reading,$value,$unit) = DbLog_cutCol($hash->{dbloghash},$device,$type,$event,$reading,$value,$unit);
  7050. push(@row_array, "$date $time|$device|$type|$event|$reading|$value|$unit");
  7051. }
  7052. }
  7053. }
  7054. if (@row_array) {
  7055. # Schreibzyklus aktivieren
  7056. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => $utf8 });};
  7057. if ($@) {
  7058. $err = $@;
  7059. Log3 ($name, 2, "DbRep $name - $@");
  7060. return ($wrt,$irowdone,$err);
  7061. }
  7062. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  7063. my ($usepkh,$usepkc,$pkh,$pkc);
  7064. if (!$supk) {
  7065. ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh);
  7066. } else {
  7067. Log3 $hash->{NAME}, 5, "DbRep $name -> Primary Key usage suppressed by attribute noSupportPK in DbLog \"$dblogname\"";
  7068. }
  7069. if (lc($DbLogType) =~ m(history)) {
  7070. # insert history mit/ohne primary key
  7071. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  7072. eval { $sth_ih = $dbh->prepare_cached("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7073. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  7074. eval { $sth_ih = $dbh->prepare_cached("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7075. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  7076. eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  7077. } else {
  7078. eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7079. }
  7080. if ($@) {
  7081. $err = $@;
  7082. Log3 ($name, 2, "DbRep $name - $@");
  7083. return ($wrt,$irowdone,$err);
  7084. }
  7085. # update history mit/ohne primary key
  7086. if ($usepkh && $hash->{MODEL} eq 'MYSQL') {
  7087. $sth_uh = $dbh->prepare("REPLACE INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?)");
  7088. } elsif ($usepkh && $hash->{MODEL} eq 'SQLITE') {
  7089. $sth_uh = $dbh->prepare("INSERT OR REPLACE INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?)");
  7090. } elsif ($usepkh && $hash->{MODEL} eq 'POSTGRESQL') {
  7091. $sth_uh = $dbh->prepare("INSERT INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  7092. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  7093. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  7094. } else {
  7095. $sth_uh = $dbh->prepare("UPDATE history SET TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (TIMESTAMP=?) AND (DEVICE=?) AND (READING=?)");
  7096. }
  7097. }
  7098. if (lc($DbLogType) =~ m(current) ) {
  7099. # insert current mit/ohne primary key
  7100. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  7101. eval { $sth_ic = $dbh->prepare("INSERT IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7102. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  7103. eval { $sth_ic = $dbh->prepare("INSERT OR IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7104. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  7105. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  7106. } else {
  7107. # old behavior
  7108. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7109. }
  7110. if ($@) {
  7111. $err = $@;
  7112. Log3 ($name, 2, "DbRep $name - $@");
  7113. return ($wrt,$irowdone,$err);
  7114. }
  7115. # update current mit/ohne primary key
  7116. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  7117. $sth_uc = $dbh->prepare("REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  7118. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  7119. $sth_uc = $dbh->prepare("INSERT OR REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  7120. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  7121. $sth_uc = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  7122. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  7123. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  7124. } else {
  7125. $sth_uc = $dbh->prepare("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)");
  7126. }
  7127. }
  7128. eval { $dbh->begin_work() if($dbh->{AutoCommit}); };
  7129. if ($@) {
  7130. Log3($name, 2, "DbRep $name -> Error start transaction for history - $@");
  7131. }
  7132. Log3 $hash->{NAME}, 5, "DbRep $name - data prepared to db write:";
  7133. # SQL-Startzeit
  7134. my $wst = [gettimeofday];
  7135. my $ihs = 0;
  7136. my $uhs = 0;
  7137. foreach my $row (@row_array) {
  7138. my @a = split("\\|",$row);
  7139. $timestamp = $a[0];
  7140. $device = $a[1];
  7141. $type = $a[2];
  7142. $event = $a[3];
  7143. $reading = $a[4];
  7144. $value = $a[5];
  7145. $unit = $a[6];
  7146. Log3 $hash->{NAME}, 5, "DbRep $name - $row";
  7147. eval {
  7148. # update oder insert history
  7149. if (lc($DbLogType) =~ m(history) ) {
  7150. my $rv_uh = $sth_uh->execute($type,$event,$value,$unit,$timestamp,$device,$reading);
  7151. if ($rv_uh == 0) {
  7152. $sth_ih->execute($timestamp,$device,$type,$event,$reading,$value,$unit);
  7153. $ihs++;
  7154. } else {
  7155. $uhs++;
  7156. }
  7157. }
  7158. # update oder insert current
  7159. if (lc($DbLogType) =~ m(current) ) {
  7160. my $rv_uc = $sth_uc->execute($timestamp,$type,$event,$value,$unit,$device,$reading);
  7161. if ($rv_uc == 0) {
  7162. $sth_ic->execute($timestamp,$device,$type,$event,$reading,$value,$unit);
  7163. }
  7164. }
  7165. };
  7166. if ($@) {
  7167. $err = $@;
  7168. Log3 ($name, 2, "DbRep $name - $@");
  7169. $dbh->rollback;
  7170. $dbh->disconnect;
  7171. $ihs = 0;
  7172. $uhs = 0;
  7173. return ($wrt,0,$err);
  7174. } else {
  7175. $irowdone++;
  7176. }
  7177. }
  7178. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  7179. $dbh->disconnect;
  7180. Log3 $hash->{NAME}, 3, "DbRep $name - number of lines updated in \"$dblogname\": $uhs";
  7181. Log3 $hash->{NAME}, 3, "DbRep $name - number of lines inserted into \"$dblogname\": $ihs";
  7182. # SQL-Laufzeit ermitteln
  7183. $wrt = tv_interval($wst);
  7184. }
  7185. return ($wrt,$irowdone,$err);
  7186. }
  7187. ####################################################################################################
  7188. # Werte eines Array in DB schreiben
  7189. # Übergabe-Array: $date_ESC_$time_ESC_$device_ESC_$type_ESC_$event_ESC_$reading_ESC_$value_ESC_$unit
  7190. # $histupd = 1 wenn history update, $histupd = 0 nur gistory insert
  7191. #
  7192. ####################################################################################################
  7193. sub DbRep_WriteToDB($$$@) {
  7194. my ($name,$dbh,$dbloghash,$histupd,@row_array) = @_;
  7195. my $hash = $defs{$name};
  7196. my $dblogname = $dbloghash->{NAME};
  7197. my $DbLogType = AttrVal($dbloghash->{NAME}, "DbLogType", "History");
  7198. my $supk = AttrVal($dbloghash->{NAME}, "noSupportPK", 0);
  7199. my $wrt = 0;
  7200. my $irowdone = 0;
  7201. my ($sth_ih,$sth_uh,$sth_ic,$sth_uc,$err);
  7202. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  7203. my ($usepkh,$usepkc,$pkh,$pkc);
  7204. if (!$supk) {
  7205. ($usepkh,$usepkc,$pkh,$pkc) = DbRep_checkUsePK($hash,$dbloghash,$dbh);
  7206. } else {
  7207. Log3 $hash->{NAME}, 5, "DbRep $name -> Primary Key usage suppressed by attribute noSupportPK in DbLog \"$dblogname\"";
  7208. }
  7209. if (lc($DbLogType) =~ m(history)) {
  7210. # insert history mit/ohne primary key
  7211. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  7212. eval { $sth_ih = $dbh->prepare_cached("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7213. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  7214. eval { $sth_ih = $dbh->prepare_cached("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7215. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  7216. eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  7217. } else {
  7218. eval { $sth_ih = $dbh->prepare_cached("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7219. }
  7220. if ($@) {
  7221. $err = $@;
  7222. Log3 ($name, 2, "DbRep $name - $@");
  7223. return ($wrt,$irowdone,$err);
  7224. }
  7225. # update history mit/ohne primary key
  7226. if ($usepkh && $dbloghash->{MODEL} eq 'MYSQL') {
  7227. $sth_uh = $dbh->prepare("REPLACE INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?)");
  7228. } elsif ($usepkh && $dbloghash->{MODEL} eq 'SQLITE') {
  7229. $sth_uh = $dbh->prepare("INSERT OR REPLACE INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?)");
  7230. } elsif ($usepkh && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  7231. $sth_uh = $dbh->prepare("INSERT INTO history (TYPE, EVENT, VALUE, UNIT, TIMESTAMP, DEVICE, READING) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  7232. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  7233. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  7234. } else {
  7235. $sth_uh = $dbh->prepare("UPDATE history SET TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (TIMESTAMP=?) AND (DEVICE=?) AND (READING=?)");
  7236. }
  7237. }
  7238. if (lc($DbLogType) =~ m(current) ) {
  7239. # insert current mit/ohne primary key
  7240. if ($usepkc && $dbloghash->{MODEL} eq 'MYSQL') {
  7241. eval { $sth_ic = $dbh->prepare("INSERT IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7242. } elsif ($usepkc && $dbloghash->{MODEL} eq 'SQLITE') {
  7243. eval { $sth_ic = $dbh->prepare("INSERT OR IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7244. } elsif ($usepkc && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  7245. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  7246. } else {
  7247. # old behavior
  7248. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  7249. }
  7250. if ($@) {
  7251. $err = $@;
  7252. Log3 ($name, 2, "DbRep $name - $@");
  7253. return ($wrt,$irowdone,$err);
  7254. }
  7255. # update current mit/ohne primary key
  7256. if ($usepkc && $dbloghash->{MODEL} eq 'MYSQL') {
  7257. $sth_uc = $dbh->prepare("REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  7258. } elsif ($usepkc && $dbloghash->{MODEL} eq 'SQLITE') {
  7259. $sth_uc = $dbh->prepare("INSERT OR REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  7260. } elsif ($usepkc && $dbloghash->{MODEL} eq 'POSTGRESQL') {
  7261. $sth_uc = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  7262. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  7263. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  7264. } else {
  7265. $sth_uc = $dbh->prepare("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)");
  7266. }
  7267. }
  7268. eval { $dbh->begin_work() if($dbh->{AutoCommit}); };
  7269. if ($@) {
  7270. Log3($name, 2, "DbRep $name -> Error start transaction for history - $@");
  7271. }
  7272. Log3 $hash->{NAME}, 5, "DbRep $name - data prepared to db write:";
  7273. # SQL-Startzeit
  7274. my $wst = [gettimeofday];
  7275. my $ihs = 0;
  7276. my $uhs = 0;
  7277. foreach my $row (@row_array) {
  7278. my ($date,$time,$device,$type,$event,$reading,$value,$unit) = ($row =~ /^(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)_ESC_(.*)$/);
  7279. Log3 $hash->{NAME}, 5, "DbRep $name - $row";
  7280. my $timestamp = $date." ".$time;
  7281. eval {
  7282. # update oder insert history
  7283. if (lc($DbLogType) =~ m(history) ) {
  7284. my $rv_uh = 0;
  7285. if($histupd) {
  7286. $rv_uh = $sth_uh->execute($type,$event,$value,$unit,$timestamp,$device,$reading);
  7287. }
  7288. if ($rv_uh == 0) {
  7289. $sth_ih->execute($timestamp,$device,$type,$event,$reading,$value,$unit);
  7290. $ihs++;
  7291. } else {
  7292. $uhs++;
  7293. }
  7294. }
  7295. # update oder insert current
  7296. if (lc($DbLogType) =~ m(current) ) {
  7297. my $rv_uc = $sth_uc->execute($timestamp,$type,$event,$value,$unit,$device,$reading);
  7298. if ($rv_uc == 0) {
  7299. $sth_ic->execute($timestamp,$device,$type,$event,$reading,$value,$unit);
  7300. }
  7301. }
  7302. };
  7303. if ($@) {
  7304. $err = $@;
  7305. Log3 ($name, 2, "DbRep $name - $@");
  7306. $dbh->rollback;
  7307. $ihs = 0;
  7308. $uhs = 0;
  7309. return ($wrt,0,$err);
  7310. } else {
  7311. $irowdone++;
  7312. }
  7313. }
  7314. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  7315. Log3 $hash->{NAME}, 3, "DbRep $name - number of lines updated in \"$dblogname\": $uhs" if($uhs);
  7316. Log3 $hash->{NAME}, 3, "DbRep $name - number of lines inserted into \"$dblogname\": $ihs" if($ihs);
  7317. # SQL-Laufzeit ermitteln
  7318. $wrt = tv_interval($wst);
  7319. return ($wrt,$irowdone,$err);
  7320. }
  7321. ################################################################
  7322. # check ob primary key genutzt wird
  7323. ################################################################
  7324. sub DbRep_checkUsePK ($$$){
  7325. my ($hash,$dbloghash,$dbh) = @_;
  7326. my $name = $hash->{NAME};
  7327. my $dbconn = $dbloghash->{dbconn};
  7328. my $upkh = 0;
  7329. my $upkc = 0;
  7330. my (@pkh,@pkc);
  7331. my $db = (split("=",(split(";",$dbconn))[0]))[1];
  7332. eval {@pkh = $dbh->primary_key( undef, undef, 'history' );};
  7333. eval {@pkc = $dbh->primary_key( undef, undef, 'current' );};
  7334. my $pkh = (!@pkh || @pkh eq "")?"none":join(",",@pkh);
  7335. my $pkc = (!@pkc || @pkc eq "")?"none":join(",",@pkc);
  7336. $pkh =~ tr/"//d;
  7337. $pkc =~ tr/"//d;
  7338. $upkh = 1 if(@pkh && @pkh ne "none");
  7339. $upkc = 1 if(@pkc && @pkc ne "none");
  7340. Log3 $hash->{NAME}, 5, "DbRep $name -> Primary Key used in $db.history: $pkh";
  7341. Log3 $hash->{NAME}, 5, "DbRep $name -> Primary Key used in $db.current: $pkc";
  7342. return ($upkh,$upkc,$pkh,$pkc);
  7343. }
  7344. ################################################################
  7345. # extrahiert aus dem übergebenen Wert nur die Zahl
  7346. ################################################################
  7347. sub DbRep_numval ($){
  7348. my ($val) = @_;
  7349. return undef if(!defined($val));
  7350. $val = ($val =~ /(-?\d+(\.\d+)?)/ ? $1 : "");
  7351. return $val;
  7352. }
  7353. ####################################################################################################
  7354. # Browser Refresh nach DB-Abfrage
  7355. ####################################################################################################
  7356. sub browser_refresh($) {
  7357. my ($hash) = @_;
  7358. RemoveInternalTimer($hash, "browser_refresh");
  7359. {FW_directNotify("#FHEMWEB:WEB", "location.reload('true')", "")};
  7360. # map { FW_directNotify("#FHEMWEB:$_", "location.reload(true)", "") } devspec2array("WEB.*");
  7361. return;
  7362. }
  7363. ####################################################################################################
  7364. # Test-Sub zu Testzwecken
  7365. ####################################################################################################
  7366. sub testexit ($) {
  7367. my ($hash) = @_;
  7368. my $name = $hash->{NAME};
  7369. if ( !DbRep_Connect($hash) ) {
  7370. Log3 ($name, 2, "DbRep $name - DB connect failed. Database down ? ");
  7371. ReadingsSingleUpdateValue ($hash, "state", "disconnected", 1);
  7372. return;
  7373. } else {
  7374. my $dbh = $hash->{DBH};
  7375. Log3 ($name, 3, "DbRep $name - --------------- FILE INFO --------------");
  7376. my $sqlfile = $dbh->sqlite_db_filename();
  7377. Log3 ($name, 3, "DbRep $name - FILE : $sqlfile ");
  7378. # # $dbh->table_info( $catalog, $schema, $table)
  7379. # my $sth = $dbh->table_info('', '%', '%');
  7380. # my $tables = $dbh->selectcol_arrayref($sth, {Columns => [3]});
  7381. # my $table = join ', ', @$tables;
  7382. # Log3 ($name, 3, "DbRep $name - SQL_TABLES : $table");
  7383. Log3 ($name, 3, "DbRep $name - --------------- PRAGMA --------------");
  7384. my @InfoTypes = ('sqlite_db_status');
  7385. foreach my $row (@InfoTypes) {
  7386. # my @linehash = $dbh->$row;
  7387. my $array= $dbh->$row ;
  7388. # push(@row_array, @array);
  7389. while ((my $key, my $val) = each %{$array}) {
  7390. Log3 ($name, 3, "DbRep $name - PRAGMA : $key : ".%{$val});
  7391. }
  7392. }
  7393. # $sth->finish;
  7394. $dbh->disconnect;
  7395. }
  7396. return;
  7397. }
  7398. 1;
  7399. =pod
  7400. =item helper
  7401. =item summary Reporting & Management content of DbLog-DB's. Content is depicted as readings
  7402. =item summary_DE Reporting & Management von DbLog-DB Content. Darstellung als Readings
  7403. =begin html
  7404. <a name="DbRep"></a>
  7405. <h3>DbRep</h3>
  7406. <ul>
  7407. <br>
  7408. 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
  7409. 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>
  7410. All database operations are implemented nonblocking. Optional the execution time of SQL-statements in background can also be determined and provided as reading.
  7411. (refer to <a href="#DbRepattr">attributes</a>). <br>
  7412. All existing readings will be deleted when a new operation starts. By attribute "readingPreventFromDel" a comma separated list of readings which are should prevent
  7413. from deletion can be provided. <br><br>
  7414. Currently the following functions are provided: <br><br>
  7415. <ul><ul>
  7416. <li> Selection of all datasets within adjustable time limits. </li>
  7417. <li> Exposure of datasets of a Device/Reading-combination within adjustable time limits. </li>
  7418. <li> Selection of datasets by usage of dynamically calclated time limits at execution time. </li>
  7419. <li> Highlighting doublets when select and display datasets (fetchrows) </li>
  7420. <li> Calculation of quantity of datasets of a Device/Reading-combination within adjustable time limits and several aggregations. </li>
  7421. <li> The calculation of summary-, difference-, maximum-, minimum- and averageValues of numeric readings within adjustable time limits and several aggregations. </li>
  7422. <li> write back results of summary-, difference-, maximum-, minimum- and average calculation into the database </li>
  7423. <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>
  7424. <li> export of datasets to file (CSV-format). </li>
  7425. <li> import of datasets from file (CSV-Format). </li>
  7426. <li> rename of device/readings in datasets </li>
  7427. <li> change of reading values in the database </li>
  7428. <li> automatic rename of device names in datasets and other DbRep-definitions after FHEM "rename" command (see <a href="#DbRepAutoRename">DbRep-Agent</a>) </li>
  7429. <li> Execution of arbitrary user specific SQL-commands </li>
  7430. <li> creation of backups of the database in running state non-blocking (MySQL, SQLite) </li>
  7431. <li> transfer dumpfiles to a FTP server after backup incl. version control</li>
  7432. <li> restore of SQLite-dumps and MySQL serverSide-backups non-blocking </li>
  7433. <li> optimize the connected database (optimizeTables, vacuum) </li>
  7434. <li> report of existing database processes (MySQL) </li>
  7435. <li> purge content of current-table </li>
  7436. <li> fill up the current-table with a (tunable) extract of the history-table</li>
  7437. <li> delete consecutive datasets with different timestamp but same values (clearing up consecutive doublets) </li>
  7438. <li> Repair of a corrupted SQLite database ("database disk image is malformed") </li>
  7439. <li> transmission of datasets from source database into another (Standby) database (syncStandby) </li>
  7440. </ul></ul>
  7441. <br>
  7442. 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.
  7443. Please read more in section <a href="#DbRepAutoRename">DbRep-Agent</a> . <br><br>
  7444. DbRep provides a UserExit function. By that interface the user can execute own program code dependent from free
  7445. definable Reading/Value-combinations (Regex). The interface works without respectively independent from event
  7446. generation.
  7447. Further informations you can find as described at <a href="#DbRepattr">attribute</a> "userExitFn". <br><br>
  7448. FHEM-Forum: <br>
  7449. <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>
  7450. <br>
  7451. <b>Preparations </b> <br><br>
  7452. The module requires the usage of a DbLog instance and the credentials of the database definition will be used. <br>
  7453. Only the content of table "history" will be included if isn't other is explained. <br><br>
  7454. Overview which other Perl-modules DbRep is using: <br><br>
  7455. Net::FTP (only if FTP-Transfer after database dump is used) <br>
  7456. Net::FTPSSL (only if FTP-Transfer with encoding after database dump is used) <br>
  7457. POSIX <br>
  7458. Time::HiRes <br>
  7459. Time::Local <br>
  7460. Scalar::Util <br>
  7461. DBI <br>
  7462. Color (FHEM-module) <br>
  7463. IO::Compress::Gzip <br>
  7464. IO::Uncompress::Gunzip <br>
  7465. Blocking (FHEM-module) <br><br>
  7466. Due to performance reason the following index should be created in addition: <br>
  7467. <code>
  7468. CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;
  7469. </code>
  7470. </ul>
  7471. <br>
  7472. <a name="DbRepdefine"></a>
  7473. <b>Definition</b>
  7474. <br>
  7475. <ul>
  7476. <code>
  7477. define &lt;name&gt; DbRep &lt;name of DbLog-instance&gt;
  7478. </code>
  7479. <br><br>
  7480. (&lt;name of DbLog-instance&gt; - name of the database instance which is wanted to analyze needs to be inserted)
  7481. </ul>
  7482. <br><br>
  7483. <a name="DbRepset"></a>
  7484. <b>Set </b>
  7485. <ul>
  7486. Currently following set-commands are included. They are used to trigger the evaluations and define the evaluation option option itself.
  7487. The criteria of searching database content and determine aggregation is carried out by setting several <a href="#DbRepattr">attributes</a>.
  7488. <br><br>
  7489. <ul><ul>
  7490. <li><b> averageValue [display | writeToDB]</b>
  7491. - calculates the average value of database column "VALUE" between period given by
  7492. timestamp-<a href="#DbRepattr">attributes</a> which are set.
  7493. The reading to evaluate must be specified by attribute "reading". <br>
  7494. By attribute "averageCalcForm" the calculation variant for average determination will be configured.
  7495. At the moment the following methods are implemented: <br><br>
  7496. <ul>
  7497. <table>
  7498. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7499. <tr><td> <b>avgArithmeticMean</b> </td><td>: the arithmetic average is calculated (default) </td></tr>
  7500. <tr><td> <b>avgDailyMeanGWS</b> </td><td>: calculates the daily medium temperature according the
  7501. <a href='https://www.dwd.de/DE/leistungen/klimadatendeutschland/beschreibung_tagesmonatswerte.html'>specifications</a> of german weather service. <br>
  7502. This variant uses aggregation "day" automatically. </td></tr>
  7503. <tr><td> <b>avgTimeWeightMean</b> </td><td>: calcultes a time weighted average mean value is calculated </td></tr>
  7504. </table>
  7505. </ul>
  7506. <br>
  7507. Is no or the option "display" specified, the results are only displayed. Using
  7508. option "writeToDB" the calculated results are stored in the database with a new reading
  7509. name. <br>
  7510. The new readingname is built of a prefix and the original reading name.
  7511. The prefix is made up of the creation function and the aggregation. <br>
  7512. The timestamp of the new stored readings is deviated from aggregation period,
  7513. unless no unique point of time of the result can be determined.
  7514. The field "EVENT" will be filled with "calculated".<br><br>
  7515. <ul>
  7516. <b>Example of building a new reading name from the original reading "totalpac":</b> <br>
  7517. avgam_day_totalpac <br>
  7518. # &lt;creation function&gt;_&lt;aggregation&gt;_&lt;original reading&gt; <br>
  7519. </ul>
  7520. </li><br>
  7521. <li><b> cancelDump </b> - stops a running database dump. </li> <br>
  7522. <li><b> changeValue </b> - changes the saved value of readings.
  7523. If the selection is limited to particular device/reading-combinations by
  7524. <a href="#DbRepattr">attribute</a> "device" respectively "reading", it is considered as well
  7525. as possibly defined time limits by time attributes (time.*). <br>
  7526. If no limits are set, the whole database is scanned and the specified value will be
  7527. changed. <br><br>
  7528. <ul>
  7529. <b>Syntax: </b> <br>
  7530. set &lt;name&gt; changeValue "&lt;old string&gt;","&lt;new string&gt;" <br><br>
  7531. The strings have to be quoted and separated by comma.
  7532. A "string" can be: <br>
  7533. <pre>
  7534. &lt;old string&gt; : * a simple string with/without spaces, e.g. "OL 12"
  7535. * a string with usage of SQL-wildcard, e.g. "%OL%"
  7536. &lt;new string&gt; : * a simple string with/without spaces, e.g. "12 kWh"
  7537. * Perl code embedded in {}, e.g. {$VALUE = (split(",",$VALUE))[1]}.
  7538. The perl expression the variables $VALUE and $UNIT are committed to. The variables are changable within
  7539. the perl code. The returned value of VALUE and UNIT are saved into the database field
  7540. VALUE respectively UNIT of the dataset.
  7541. </pre>
  7542. <b>Examples: </b> <br>
  7543. set &lt;name&gt; changeValue "OL","12 OL" <br>
  7544. # the old field value "OL" is changed to "12 OL". <br><br>
  7545. set &lt;name&gt; changeValue "%OL%","12 OL" <br>
  7546. # contains the field VALUE the substring "OL", it is changed to "12 OL". <br><br>
  7547. set &lt;name&gt; changeValue "12 kWh","{$VALUE,$UNIT = split(" ",$VALUE)}" <br>
  7548. # the old field value "12 kWh" is splitted to VALUE=12 and UNIT=kWh and saved into the database fields <br><br>
  7549. set &lt;name&gt; changeValue "24%","{$VALUE = (split(" ",$VALUE))[0]}" <br>
  7550. # if the old field value begins with "24", it is splitted and VALUE=24 is saved (e.g. "24 kWh")
  7551. <br><br>
  7552. Summarized the relevant attributes to control function changeValue are: <br><br>
  7553. <ul>
  7554. <table>
  7555. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7556. <tr><td> <b>device</b> </td><td>: selection only of datasets which contain &lt;device&gt; </td></tr>
  7557. <tr><td> <b>reading</b> </td><td>: selection only of datasets which contain &lt;reading&gt; </td></tr>
  7558. <tr><td> <b>time.*</b> </td><td>: a number of attributes to limit selection by time </td></tr>
  7559. <tr><td> <b>executeBeforeProc</b> </td><td>: execute a FHEM command (or perl-routine) before start of changeValue </td></tr>
  7560. <tr><td> <b>executeAfterProc</b> </td><td>: execute a FHEM command (or perl-routine) after changeValue is finished </td></tr>
  7561. </table>
  7562. </ul>
  7563. <br>
  7564. <br>
  7565. <b>Note:</b> <br>
  7566. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  7567. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  7568. </li> <br>
  7569. </ul>
  7570. <li><b> countEntries [history|current] </b> - provides the number of table-entries (default: history) between period set
  7571. by timestamp-<a href="#DbRepattr">attributes</a> if set.
  7572. If timestamp-attributes are not set, all entries of the table will be count.
  7573. The <a href="#DbRepattr">attributes</a> "device" and "reading" can be used to
  7574. limit the evaluation. </li> <br>
  7575. <li><b> delEntries </b> - deletes all database entries or only the database entries specified by <a href="#DbRepattr">attributes</a> Device and/or
  7576. Reading and the entered time period between "timestamp_begin", "timestamp_end" (if set) or "timeDiffToNow/timeOlderThan". <br><br>
  7577. <ul>
  7578. "timestamp_begin" is set: deletes db entries <b>from</b> this timestamp until current date/time <br>
  7579. "timestamp_end" is set : deletes db entries <b>until</b> this timestamp <br>
  7580. both Timestamps are set : deletes db entries <b>between</b> these timestamps <br><br>
  7581. Due to security reasons the attribute <a href="#DbRepattr">attribute</a> "allowDeletion" needs to be set to unlock the
  7582. delete-function. <br>
  7583. </li>
  7584. <br>
  7585. </ul>
  7586. <li><b> delSeqDoublets [adviceRemain | adviceDelete | delete]</b> - show respectively delete identical sequentially datasets.
  7587. Therefore Device,Reading and Value of the sequentially datasets are compared.
  7588. Not deleted are the first und the last dataset of a aggregation period (e.g. hour,day,week and so on) as
  7589. well as the datasets before or after a value change (database field VALUE). <br>
  7590. The <a href="#DbRepattr">attributes</a> to define the scope of aggregation,time period, device and reading are
  7591. considered. If attribute aggregation is not set or set to "no", it will change to the default aggregation
  7592. period "day". For datasets containing numerical values it is possible to determine a variance with <a href="#DbRepattr">attribute</a>
  7593. "seqDoubletsVariance". Up to this value consecutive numerical datasets are handled as identical and should be
  7594. deleted.
  7595. <br><br>
  7596. <ul>
  7597. <table>
  7598. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7599. <tr><td> <b>adviceRemain</b> </td><td>: simulates the remaining datasets in database after delete-operation (nothing will be deleted !) </td></tr>
  7600. <tr><td> <b>adviceDelete</b> </td><td>: simulates the datasets to delete in database (nothing will be deleted !) </td></tr>
  7601. <tr><td> <b>delete</b> </td><td>: deletes the consecutive doublets (see example) </td></tr>
  7602. </table>
  7603. </ul>
  7604. <br>
  7605. Due to security reasons the attribute <a href="#DbRepattr">attribute</a> "allowDeletion" needs to be set for
  7606. execute the "delete" option. <br>
  7607. The amount of datasets to show by commands "delSeqDoublets adviceDelete", "delSeqDoublets adviceRemain" is
  7608. initially limited (default: 1000) and can be adjusted by <a href="#DbRepattr">attribute</a> "limit".
  7609. The adjustment of "limit" has no impact to the "delSeqDoublets delete" function, but affects <b>ONLY</b> the
  7610. display of the data. <br>
  7611. Before and after this "delSeqDoublets" it is possible to execute a FHEM command or Perl-script
  7612. (please see <a href="#DbRepattr">attributes</a> "executeBeforeProc" and "executeAfterProc").
  7613. <br><br>
  7614. <ul>
  7615. <b>Example</b> - the remaining datasets after executing delete-option are are marked as <b>bold</b>: <br><br>
  7616. <ul>
  7617. <b>2017-11-25_00-00-05__eg.az.fridge_Pwr__power 0 </b> <br>
  7618. 2017-11-25_00-02-26__eg.az.fridge_Pwr__power 0 <br>
  7619. 2017-11-25_00-04-33__eg.az.fridge_Pwr__power 0 <br>
  7620. 2017-11-25_01-06-10__eg.az.fridge_Pwr__power 0 <br>
  7621. <b>2017-11-25_01-08-21__eg.az.fridge_Pwr__power 0 </b> <br>
  7622. <b>2017-11-25_01-08-59__eg.az.fridge_Pwr__power 60.32 </b> <br>
  7623. <b>2017-11-25_01-11-21__eg.az.fridge_Pwr__power 56.26 </b> <br>
  7624. <b>2017-11-25_01-27-54__eg.az.fridge_Pwr__power 6.19 </b> <br>
  7625. <b>2017-11-25_01-28-51__eg.az.fridge_Pwr__power 0 </b> <br>
  7626. 2017-11-25_01-31-00__eg.az.fridge_Pwr__power 0 <br>
  7627. 2017-11-25_01-33-59__eg.az.fridge_Pwr__power 0 <br>
  7628. <b>2017-11-25_02-39-29__eg.az.fridge_Pwr__power 0 </b> <br>
  7629. <b>2017-11-25_02-41-18__eg.az.fridge_Pwr__power 105.28</b> <br>
  7630. <b>2017-11-25_02-41-26__eg.az.fridge_Pwr__power 61.52 </b> <br>
  7631. <b>2017-11-25_03-00-06__eg.az.fridge_Pwr__power 47.46 </b> <br>
  7632. <b>2017-11-25_03-00-33__eg.az.fridge_Pwr__power 0 </b> <br>
  7633. 2017-11-25_03-02-07__eg.az.fridge_Pwr__power 0 <br>
  7634. 2017-11-25_23-37-42__eg.az.fridge_Pwr__power 0 <br>
  7635. <b>2017-11-25_23-40-10__eg.az.fridge_Pwr__power 0 </b> <br>
  7636. <b>2017-11-25_23-42-24__eg.az.fridge_Pwr__power 1 </b> <br>
  7637. 2017-11-25_23-42-24__eg.az.fridge_Pwr__power 1 <br>
  7638. <b>2017-11-25_23-45-27__eg.az.fridge_Pwr__power 1 </b> <br>
  7639. <b>2017-11-25_23-47-07__eg.az.fridge_Pwr__power 0 </b> <br>
  7640. 2017-11-25_23-55-27__eg.az.fridge_Pwr__power 0 <br>
  7641. <b>2017-11-25_23-48-15__eg.az.fridge_Pwr__power 0 </b> <br>
  7642. <b>2017-11-25_23-50-21__eg.az.fridge_Pwr__power 59.1 </b> <br>
  7643. <b>2017-11-25_23-55-14__eg.az.fridge_Pwr__power 52.31 </b> <br>
  7644. <b>2017-11-25_23-58-09__eg.az.fridge_Pwr__power 51.73 </b> <br>
  7645. </ul>
  7646. </ul>
  7647. </li>
  7648. <br>
  7649. <br>
  7650. <li><b> deviceRename </b> - renames the device name of a device inside the connected database (Internal DATABASE).
  7651. The devicename will allways be changed in the <b>entire</b> database. Possibly set time limits or restrictions by
  7652. <a href="#DbRepattr">attributes</a> device and/or reading will not be considered. <br><br>
  7653. <ul>
  7654. <b>Example: </b> <br>
  7655. set &lt;name&gt; deviceRename &lt;old device name&gt;,&lt;new device name&gt; <br>
  7656. # The amount of renamed device names (datasets) will be displayed in reading "device_renamed". <br>
  7657. # If the device name to be renamed was not found in the database, a WARNUNG will appear in reading "device_not_renamed". <br>
  7658. # Appropriate entries will be written to Logfile if verbose >= 3 is set.
  7659. <br><br>
  7660. <b>Note:</b> <br>
  7661. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  7662. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  7663. </li> <br>
  7664. </ul>
  7665. <li><b> diffValue [display | writeToDB]</b>
  7666. - calculates the difference of database column "VALUE" between period given by
  7667. <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  7668. The reading to evaluate must be defined using attribute "reading".
  7669. This function is mostly reasonable if readingvalues are increasing permanently and don't write value-differences to the database.
  7670. The difference will be generated from the first available dataset (VALUE-Field) to the last available dataset between the
  7671. specified time linits/aggregation, in which a balanced difference value of the previous aggregation period will be transfered to the
  7672. following aggregation period in case this period contains a value. <br>
  7673. An possible counter overrun (restart with value "0") will be considered (compare <a href="#DbRepattr">attribute</a> "diffAccept"). <br><br>
  7674. If only one dataset will be found within the evalution period, the difference can be calculated only in combination with the balanced
  7675. difference of the previous aggregation period. In this case a logical inaccuracy according the assignment of the difference to the particular aggregation period
  7676. can be possible. Hence in warning in "state" will be placed and the reading "less_data_in_period" with a list of periods
  7677. with only one dataset found in it will be created.
  7678. <br><br>
  7679. <ul>
  7680. <b>Note: </b><br>
  7681. Within the evaluation respectively aggregation period (day, week, month, etc.) you should make available at least one dataset
  7682. at the beginning and one dataset at the end of each aggregation period to take the difference calculation as much as possible.
  7683. <br>
  7684. <br>
  7685. </ul>
  7686. Is no or the option "display" specified, the results are only displayed. Using
  7687. option "writeToDB" the calculation results are stored in the database with a new reading
  7688. name. <br>
  7689. The new readingname is built of a prefix and the original reading name.
  7690. The prefix is made up of the creation function and the aggregation. <br>
  7691. The timestamp of the new stored readings is deviated from aggregation period,
  7692. unless no unique point of time of the result can be determined.
  7693. The field "EVENT" will be filled with "calculated".<br><br>
  7694. <ul>
  7695. <b>Example of building a new reading name from the original reading "totalpac":</b> <br>
  7696. diff_day_totalpac <br>
  7697. # &lt;creation function&gt;_&lt;aggregation&gt;_&lt;original reading&gt; <br>
  7698. </ul>
  7699. </li><br>
  7700. <li><b> dumpMySQL [clientSide | serverSide]</b>
  7701. - creates a dump of the connected MySQL database. <br>
  7702. Depending from selected option the dump will be created on Client- or on Server-Side. <br>
  7703. The variants differs each other concerning the executing system, the creating location, the usage of
  7704. attributes, the function result and the needed hardware ressources. <br>
  7705. The option "clientSide" e.g. needs more powerful FHEM-Server hardware, but saves all available
  7706. tables inclusive possibly created views. <br>
  7707. With attribute "dumpCompress" a compression of dump file after creation can be switched on.
  7708. <br><br>
  7709. <ul>
  7710. <b><u>Option clientSide</u></b> <br>
  7711. The dump will be created by client (FHEM-Server) and will be saved in FHEM log-directory by
  7712. default.
  7713. The target directory can be set by <a href="#DbRepattr">attribute</a> "dumpDirLocal" and has to be
  7714. writable by the FHEM process. <br>
  7715. Before executing the dump a table optimization can be processed optionally (see attribute
  7716. "optimizeTablesBeforeDump") as well as a FHEM-command (attribute "executeBeforeProc").
  7717. After the dump a FHEM-command can be executed as well (see attribute "executeAfterProc"). <br><br>
  7718. <b>Note: <br>
  7719. To avoid FHEM from blocking, you have to operate DbLog in asynchronous mode if the table
  7720. optimization want to be used ! </b> <br><br>
  7721. By the <a href="#DbRepattr">attributes</a> "dumpMemlimit" and "dumpSpeed" the run-time behavior of the function can be
  7722. controlled to optimize the performance and demand of ressources. <br><br>
  7723. The attributes relevant for function "dumpMySQL clientSide" are: <br><br>
  7724. <ul>
  7725. <table>
  7726. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7727. <tr><td> dumpComment </td><td>: User comment in head of dump file </td></tr>
  7728. <tr><td> dumpCompress </td><td>: compress of dump files after creation </td></tr>
  7729. <tr><td> dumpDirLocal </td><td>: the local destination directory for dump file creation </td></tr>
  7730. <tr><td> dumpMemlimit </td><td>: limits memory usage </td></tr>
  7731. <tr><td> dumpSpeed </td><td>: limits CPU utilization </td></tr>
  7732. <tr><td> dumpFilesKeep </td><td>: number of dump files to keep </td></tr>
  7733. <tr><td> executeBeforeProc </td><td>: execution of FHEM command (or perl-routine) before dump </td></tr>
  7734. <tr><td> executeAfterProc </td><td>: execution of FHEM command (or perl-routine) after dump </td></tr>
  7735. <tr><td> optimizeTablesBeforeDump </td><td>: table optimization before dump </td></tr>
  7736. </table>
  7737. </ul>
  7738. <br>
  7739. After a successfull finished dump the old dumpfiles are deleted and only the number of files
  7740. defined by attribute "dumpFilesKeep" (default: 3) remain in the target directory
  7741. "dumpDirLocal". If "dumpFilesKeep = 0" is set, all
  7742. dumpfiles (also the current created file), are deleted. This setting can be helpful, if FTP transmission is used
  7743. and the created dumps are only keep remain in the FTP destination directory. <br><br>
  7744. The <b>naming convention of dump files</b> is: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sql[.gzip] <br><br>
  7745. The created dumpfile may imported on the MySQL-Server by e.g.: <br><br>
  7746. <ul>
  7747. mysql -u &lt;user&gt; -p &lt;dbname&gt; < &lt;filename&gt;.sql <br><br>
  7748. </ul>
  7749. to restore the database from the dump. <br><br><br>
  7750. <b><u>Option serverSide</u></b> <br>
  7751. The dump will be created on the MySQL-Server and will be saved in its Home-directory
  7752. by default. <br>
  7753. The whole history-table (not the current-table) will be exported <b>CSV-formatted</b> without
  7754. any restrictions. <br>
  7755. Before executing the dump a table optimization can be processed optionally (see attribute
  7756. "optimizeTablesBeforeDump") as well as a FHEM-command (attribute "executeBeforeProc"). <br><br>
  7757. <b>Note: <br>
  7758. To avoid FHEM from blocking, you have to operate DbLog in asynchronous mode if the table
  7759. optimization want to be used ! </b> <br><br>
  7760. After the dump a FHEM-command can be executed as well (see attribute "executeAfterProc"). <br><br>
  7761. The attributes relevant for function "dumpMySQL serverSide" are: <br><br>
  7762. <ul>
  7763. <table>
  7764. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7765. <tr><td> dumpDirRemote </td><td>: destination directory of dump file on remote server </td></tr>
  7766. <tr><td> dumpCompress </td><td>: compress of dump files after creation </td></tr>
  7767. <tr><td> dumpDirLocal </td><td>: the local mounted directory dumpDirRemote </td></tr>
  7768. <tr><td> dumpFilesKeep </td><td>: number of dump files to keep </td></tr>
  7769. <tr><td> executeBeforeProc </td><td>: execution of FHEM command (or perl-routine) before dump </td></tr>
  7770. <tr><td> executeAfterProc </td><td>: execution of FHEM command (or perl-routine) after dump </td></tr>
  7771. <tr><td> optimizeTablesBeforeDump </td><td>: table optimization before dump </td></tr>
  7772. </table>
  7773. </ul>
  7774. <br>
  7775. The target directory can be set by <a href="#DbRepattr">attribute</a> "dumpDirRemote".
  7776. It must be located on the MySQL-Host and has to be writable by the MySQL-server process. <br>
  7777. The used database user must have the "FILE"-privilege. <br><br>
  7778. <b>Note:</b> <br>
  7779. If the internal version management of DbRep should be used and the size of the created dumpfile be
  7780. reported, you have to mount the remote MySQL-Server directory "dumpDirRemote" on the client
  7781. and publish it to the DbRep-device by fill out the <a href="#DbRepattr">attribute</a>
  7782. "dumpDirLocal". <br>
  7783. Same is necessary if ftp transfer after dump is to be used (attribute "ftpUse" respectively "ftpUseSSL").
  7784. <br><br>
  7785. <ul>
  7786. <b>Example: </b> <br>
  7787. attr &lt;name&gt; dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM/ <br>
  7788. attr &lt;name&gt; dumpDirLocal /sds1/backup/dumps_FHEM/ <br>
  7789. attr &lt;name&gt; dumpFilesKeep 2 <br><br>
  7790. # The dump will be created remote on the MySQL-Server in directory
  7791. '/volume1/ApplicationBackup/dumps_FHEM/'. <br>
  7792. # The internal version management searches in local mounted directory '/sds1/backup/dumps_FHEM/'
  7793. for present dumpfiles and deletes these files except the last two versions. <br>
  7794. <br>
  7795. </ul>
  7796. If the internal version management is used, after a successfull finished dump old dumpfiles will
  7797. be deleted and only the number of attribute "dumpFilesKeep" (default: 3) would remain in target
  7798. directory "dumpDirLocal" (the mounted "dumpDirRemote").
  7799. In that case FHEM needs write permissions to the directory "dumpDirLocal". <br><br>
  7800. The <b>naming convention of dump files</b> is: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.csv[.gzip] <br><br>
  7801. You can start a restore of table history from serverSide-Backup by command: <br><br>
  7802. <ul>
  7803. set &lt;name&gt; &lt;restoreMySQL&gt; &lt;filename&gt;.csv[.gzip] <br><br>
  7804. </ul>
  7805. <br><br>
  7806. <b><u>FTP-Transfer after Dump</u></b> <br>
  7807. If those possibility is be used, the <a href="#DbRepattr">attribute</a> "ftpUse" or
  7808. "ftpUseSSL" has to be set. The latter if encoding for FTP is to be used.
  7809. The module also carries the version control of dump files in FTP-destination by attribute
  7810. "ftpDumpFilesKeep". <br>
  7811. Further <a href="#DbRepattr">attributes</a> are: <br><br>
  7812. <ul>
  7813. <table>
  7814. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7815. <tr><td> ftpUse </td><td>: FTP Transfer after dump will be switched on (without SSL encoding) </td></tr>
  7816. <tr><td> ftpUser </td><td>: User for FTP-server login, default: anonymous </td></tr>
  7817. <tr><td> ftpUseSSL </td><td>: FTP Transfer with SSL encoding after dump </td></tr>
  7818. <tr><td> ftpDebug </td><td>: debugging of FTP communication for diagnostics </td></tr>
  7819. <tr><td> ftpDir </td><td>: directory on FTP-server in which the file will be send into (default: "/") </td></tr>
  7820. <tr><td> ftpDumpFilesKeep </td><td>: leave the number of dump files in FTP-destination &lt;ftpDir&gt; (default: 3) </td></tr>
  7821. <tr><td> ftpPassive </td><td>: set if passive FTP is to be used </td></tr>
  7822. <tr><td> ftpPort </td><td>: FTP-Port, default: 21 </td></tr>
  7823. <tr><td> ftpPwd </td><td>: password of FTP-User, not set by default </td></tr>
  7824. <tr><td> ftpServer </td><td>: name or IP-address of FTP-server. <b>absolutely essential !</b> </td></tr>
  7825. <tr><td> ftpTimeout </td><td>: timeout of FTP-connection in seconds (default: 30). </td></tr>
  7826. </table>
  7827. </ul>
  7828. <br>
  7829. <br>
  7830. </ul>
  7831. </li><br>
  7832. <li><b> dumpSQLite </b> - creates a dump of the connected SQLite database. <br>
  7833. This function uses the SQLite Online Backup API and allow to create a consistent backup of the
  7834. database during the normal operation.
  7835. The dump will be saved in FHEM log-directory by default.
  7836. The target directory can be defined by <a href="#DbRepattr">attribute</a> "dumpDirLocal" and
  7837. has to be writable by the FHEM process. <br>
  7838. Before executing the dump a table optimization can be processed optionally (see attribute
  7839. "optimizeTablesBeforeDump").
  7840. <br><br>
  7841. <b>Note: <br>
  7842. To avoid FHEM from blocking, you have to operate DbLog in asynchronous mode if the table
  7843. optimization want to be used ! </b> <br><br>
  7844. Before and after the dump a FHEM-command can be executed (see attribute "executeBeforeProc",
  7845. "executeAfterProc"). <br><br>
  7846. The attributes relevant for function "dumpMySQL serverSide" are: <br><br>
  7847. <ul>
  7848. <table>
  7849. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7850. <tr><td> dumpCompress </td><td>: compress of dump files after creation </td></tr>
  7851. <tr><td> dumpDirLocal </td><td>: the local mounted directory dumpDirRemote </td></tr>
  7852. <tr><td> dumpFilesKeep </td><td>: number of dump files to keep </td></tr>
  7853. <tr><td> executeBeforeProc </td><td>: execution of FHEM command (or perl-routine) before dump </td></tr>
  7854. <tr><td> executeAfterProc </td><td>: execution of FHEM command (or perl-routine) after dump </td></tr>
  7855. <tr><td> optimizeTablesBeforeDump </td><td>: table optimization before dump </td></tr>
  7856. </table>
  7857. </ul>
  7858. <br>
  7859. After a successfull finished dump the old dumpfiles are deleted and only the number of attribute
  7860. "dumpFilesKeep" (default: 3) remain in the target directory "dumpDirLocal". If "dumpFilesKeep = 0" is set, all
  7861. dumpfiles (also the current created file), are deleted. This setting can be helpful, if FTP transmission is used
  7862. and the created dumps are only keep remain in the FTP destination directory. <br><br>
  7863. The <b>naming convention of dump files</b> is: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sqlitebkp[.gzip] <br><br>
  7864. The database can be restored by command "set &lt;name&gt; restoreSQLite &lt;filename&gt;" <br>
  7865. The created dump file can be transfered to a FTP-server. Please see explanations about FTP-
  7866. transfer in topic "dumpMySQL". <br><br>
  7867. </li><br>
  7868. <li><b> eraseReadings </b> - deletes all created readings in the device, except reading "state" and readings, which are
  7869. contained in exception list defined by attribute "readingPreventFromDel".
  7870. </li><br>
  7871. <li><b> exportToFile </b> - exports DB-entries to a file in CSV-format of time period set by time attributes.
  7872. Limitations of selections can be set by <a href="#DbRepattr">attributes</a> Device and/or Reading.
  7873. The filename will be defined by <a href="#DbRepattr">attribute</a> "expimpfile". <br>
  7874. By setting attribute "aggregation" the export of datasets will be splitted into time slices
  7875. recording the specified aggregation.
  7876. Is, for example, "aggregation = month" set, the data are selected in monthly packets and written
  7877. into the exportfile. Thereby the usage of main memory is optimized if very large amount of data
  7878. is exported and avoid the "died prematurely" error.
  7879. </li> <br>
  7880. <li><b> fetchrows [history|current] </b>
  7881. - provides <b>all</b> table entries (default: history)
  7882. of time period set by time <a href="#DbRepattr">attributes</a> respectively selection conditions
  7883. by attributes "device" and "reading".
  7884. An aggregation set will <b>not</b> be considered. <br>
  7885. The direction of data selection can be determined by <a href="#DbRepattr">attribute</a>
  7886. "fetchRoute". <br><br>
  7887. Every reading of result is composed of the dataset timestring , an index, the device name
  7888. and the reading name.
  7889. The function has the capability to reconize multiple occuring datasets (doublets).
  7890. Such doublets are marked by an index > 1. <br>
  7891. Doublets can be highlighted in terms of color by setting attribut e"fetchMarkDuplicates". <br><br>
  7892. <b>Note:</b> <br>
  7893. Highlighted readings are not displayed again after restart or rereadcfg because of they are not
  7894. saved in statefile. <br><br>
  7895. This attribute is preallocated with some colors, but can be changed by colorpicker-widget: <br><br>
  7896. <ul>
  7897. <code>
  7898. attr &lt;DbRep-Device&gt; widgetOverride fetchMarkDuplicates:colorpicker
  7899. </code>
  7900. </ul>
  7901. <br>
  7902. The readings of result are composed like the following sceme: <br><br>
  7903. <ul>
  7904. <b>Example:</b> <br>
  7905. 2017-10-22_03-04-43__1__SMA_Energymeter__Bezug_WirkP_Kosten_Diff <br>
  7906. # &lt;date&gt;_&lt;time&gt;__&lt;index&gt;__&lt;device&gt;__&lt;reading&gt;
  7907. </ul>
  7908. <br>
  7909. For a better overview the relevant attributes are listed here in a table: <br><br>
  7910. <ul>
  7911. <table>
  7912. <colgroup> <col width=5%> <col width=95%> </colgroup>
  7913. <tr><td> <b>fetchRoute</b> </td><td>: direction of selection read in database </td></tr>
  7914. <tr><td> <b>limit</b> </td><td>: limits the number of datasets to select and display </td></tr>
  7915. <tr><td> <b>fetchMarkDuplicates</b> </td><td>: Highlighting of found doublets </td></tr>
  7916. <tr><td> <b>device</b> </td><td>: select only datasets which are contain &lt;device&gt; </td></tr>
  7917. <tr><td> <b>reading</b> </td><td>: select only datasets which are contain &lt;reading&gt; </td></tr>
  7918. <tr><td> <b>time.*</b> </td><td>: A number of attributes to limit selection by time </td></tr>
  7919. </table>
  7920. </ul>
  7921. <br>
  7922. <br>
  7923. <b>Note:</b> <br>
  7924. Although the module is designed non-blocking, a huge number of selection result (huge number of rows)
  7925. can overwhelm the browser session respectively FHEMWEB.
  7926. Due to the sample space can be limited by <a href="#DbRepattrlimit">attribute</a> "limit".
  7927. Of course ths attribute can be increased if your system capabilities allow a higher workload. <br><br>
  7928. </li> <br>
  7929. <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>
  7930. <ul>
  7931. <b>input format: </b> Date,Time,Value,[Unit] <br>
  7932. # Unit is optional, attributes of device, reading must be set ! <br>
  7933. # If "Value=0" has to be inserted, use "Value = 0.0" to do it. <br><br>
  7934. <b>example:</b> 2016-08-01,23:00:09,TestValue,TestUnit <br>
  7935. # Spaces are NOT allowed in fieldvalues ! <br>
  7936. <br>
  7937. <b>Note: </b><br>
  7938. Please consider to insert AT LEAST two datasets into the intended time / aggregatiom period (day, week, month, etc.) because of
  7939. it's needed by function diffValue. Otherwise no difference can be calculated and diffValue will be print out "0" for the respective period !
  7940. <br>
  7941. <br>
  7942. </li>
  7943. </ul>
  7944. <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>
  7945. <ul>
  7946. <b>dataset format: </b> "TIMESTAMP","DEVICE","TYPE","EVENT","READING","VALUE","UNIT" <br><br>
  7947. # The fields "TIMESTAMP","DEVICE","TYPE","EVENT","READING" and "VALUE" have to be set. The field "UNIT" is optional.
  7948. The file content will be imported transactional. That means all of the content will be imported or, in case of error, nothing of it.
  7949. 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.
  7950. It could lead to blocking or overload FHEM ! <br><br>
  7951. <b>Example: </b> "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573","" <br>
  7952. <br>
  7953. </li> <br>
  7954. </ul>
  7955. <li><b> maxValue [display | writeToDB]</b>
  7956. - calculates the maximum value of database column "VALUE" between period given by
  7957. <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  7958. The reading to evaluate must be defined using attribute "reading".
  7959. The evaluation contains the timestamp of the <b>last</b> appearing of the identified maximum value
  7960. within the given period. <br>
  7961. Is no or the option "display" specified, the results are only displayed. Using
  7962. option "writeToDB" the calculated results are stored in the database with a new reading
  7963. name. <br>
  7964. The new readingname is built of a prefix and the original reading name.
  7965. The prefix is made up of the creation function and the aggregation. <br>
  7966. The timestamp of the new stored readings is deviated from aggregation period,
  7967. unless no unique point of time of the result can be determined.
  7968. The field "EVENT" will be filled with "calculated".<br><br>
  7969. <ul>
  7970. <b>Example of building a new reading name from the original reading "totalpac":</b> <br>
  7971. max_day_totalpac <br>
  7972. # &lt;creation function&gt;_&lt;aggregation&gt;_&lt;original reading&gt; <br>
  7973. </ul>
  7974. </li><br>
  7975. <li><b> minValue [display | writeToDB]</b>
  7976. - calculates the minimum value of database column "VALUE" between period given by
  7977. <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or "timeDiffToNow / timeOlderThan".
  7978. The reading to evaluate must be defined using attribute "reading".
  7979. The evaluation contains the timestamp of the <b>first</b> appearing of the identified minimum
  7980. value within the given period. <br>
  7981. Is no or the option "display" specified, the results are only displayed. Using
  7982. option "writeToDB" the calculated results are stored in the database with a new reading
  7983. name. <br>
  7984. The new readingname is built of a prefix and the original reading name.
  7985. The prefix is made up of the creation function and the aggregation. <br>
  7986. The timestamp of the new stored readings is deviated from aggregation period,
  7987. unless no unique point of time of the result can be determined.
  7988. The field "EVENT" will be filled with "calculated".<br><br>
  7989. <ul>
  7990. <b>Example of building a new reading name from the original reading "totalpac":</b> <br>
  7991. min_day_totalpac <br>
  7992. # &lt;creation function&gt;_&lt;aggregation&gt;_&lt;original reading&gt; <br>
  7993. </ul>
  7994. </li><br>
  7995. <li><b> optimizeTables </b> - optimize tables in the connected database (MySQL). <br>
  7996. Before and after an optimization it is possible to execute a FHEM command.
  7997. (please see <a href="#DbRepattr">attributes</a> "executeBeforeProc", "executeAfterProc")
  7998. <br><br>
  7999. <ul>
  8000. <b>Note:</b> <br>
  8001. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  8002. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  8003. </li><br>
  8004. </ul>
  8005. <li><b> readingRename </b> - renames the reading name of a device inside the connected database (see Internal DATABASE).
  8006. The readingname will allways be changed in the <b>entire</b> database. Possibly set time limits or restrictions by
  8007. <a href="#DbRepattr">attributes</a> device and/or reading will not be considered. <br><br>
  8008. <ul>
  8009. <b>Example: </b> <br>
  8010. set &lt;name&gt; readingRename &lt;old reading name&gt;,&lt;new reading name&gt; <br>
  8011. # The amount of renamed reading names (datasets) will be displayed in reading "reading_renamed". <br>
  8012. # If the reading name to be renamed was not found in the database, a WARNUNG will appear in reading "reading_not_renamed". <br>
  8013. # Appropriate entries will be written to Logfile if verbose >= 3 is set.
  8014. <br><br>
  8015. <b>Note:</b> <br>
  8016. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  8017. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  8018. </li> <br>
  8019. </ul>
  8020. <li><b> repairSQLite </b> - repairs a corrupted SQLite database. <br>
  8021. A corruption is usally existent when the error message "database disk image is malformed"
  8022. appears in reading "state" of the connected DbLog-device.
  8023. If the command was started, the connected DbLog-device will firstly disconnected from the
  8024. database for 10 hours (36000 seconds) automatically (breakup time). After the repair is
  8025. finished, the DbLog-device will be connected to the (repaired) database immediately. <br>
  8026. As an argument the command can be completed by a differing breakup time (in seconds). <br>
  8027. The corrupted database is saved as &lt;database&gt;.corrupt in same directory. <br><br>
  8028. <ul>
  8029. <b>Example: </b><br>
  8030. set &lt;name&gt; repairSQLite <br>
  8031. # the database is trying to repair, breakup time is 10 hours <br>
  8032. set &lt;name&gt; repairSQLite 600 <br>
  8033. # the database is trying to repair, breakup time is 10 minutes
  8034. <br><br>
  8035. <b>Note:</b> <br>
  8036. It can't be guaranteed, that the repair attempt proceed successfully and no data loss will result.
  8037. Depending from corruption severity data loss may occur or the repair will fail even though
  8038. no error appears during the repair process. Please make sure a valid backup took place ! <br><br>
  8039. </li> <br>
  8040. </ul>
  8041. <li><b> restoreMySQL &lt;file&gt;.csv[.gzip] </b> - imports the content of table history from a serverSide-backup. <br>
  8042. The function provides a drop-down-list of files which can be used for restore.
  8043. Therefore you have to mount the remote directory "dumpDirRemote" of the MySQL-Server on the
  8044. Client and make it usable to the DbRep-device by setting the <a href="#DbRepattr">attribute</a>
  8045. "dumpDirLocal". <br>
  8046. All files with extension "csv[.gzip]" and if the filename is beginning with the name of the connected database
  8047. (see Internal DATABASE) are listed. <br><br>
  8048. </li><br>
  8049. <li><b> restoreSQLite &lt;File&gt;.sqlitebkp[.gzip] </b> - restores a backup of SQLite database. <br>
  8050. The function provides a drop-down-list of files which can be used for restore.
  8051. The data stored in the current database are deleted respectively overwritten.
  8052. All files with extension "sqlitebkp[.gzip]" and if the filename is beginning with the name of the connected database
  8053. will are listed. <br><br>
  8054. </li><br>
  8055. <li><b> sqlCmd </b> - executes an arbitrary user specific command. <br>
  8056. If the command contains a operation to delete data, the <a href="#DbRepattr">attribute</a>
  8057. "allowDeletion" has to be set for security reason. <br>
  8058. The statement doesn't consider limitations by attributes "device", "reading", "time.*"
  8059. respectively "aggregation". <br>
  8060. If the <a href="#DbRepattr">attribute</a> "timestamp_begin" respectively "timestamp_end"
  8061. is assumed in the statement, it is possible to use placeholder "<b>§timestamp_begin§</b>" respectively
  8062. "<b>§timestamp_end§</b>" on suitable place. <br><br>
  8063. If you want update a dataset, you have to add "TIMESTAMP=TIMESTAMP" to the update-statement to avoid changing the
  8064. original timestamp. <br><br>
  8065. <ul>
  8066. <b>Examples of SQL-statements: </b> <br><br>
  8067. <ul>
  8068. <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>
  8069. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-05-06 00:00:00" group by DEVICE </li>
  8070. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= §timestamp_begin§ group by DEVICE </li>
  8071. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE like "Te%t" order by `TIMESTAMP` desc </li>
  8072. <li>set &lt;name&gt; sqlCmd select * from history where `TIMESTAMP` > "2017-05-09 18:03:00" order by `TIMESTAMP` desc </li>
  8073. <li>set &lt;name&gt; sqlCmd select * from current order by `TIMESTAMP` desc </li>
  8074. <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>
  8075. <li>set &lt;name&gt; sqlCmd delete from current </li>
  8076. <li>set &lt;name&gt; sqlCmd delete from history where TIMESTAMP < "2016-05-06 00:00:00" </li>
  8077. <li>set &lt;name&gt; sqlCmd update history set TIMESTAMP=TIMESTAMP,VALUE='Val' WHERE VALUE='TestValue' </li>
  8078. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE = "Test" </li>
  8079. <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>
  8080. </ul>
  8081. <br>
  8082. The result of the statement will be shown in <a href="#DbRepReadings">Reading</a> "SqlResult".
  8083. The formatting of result can be choosen by <a href="#DbRepattr">attribute</a> "sqlResultFormat", as well as the used
  8084. field separator can be determined by <a href="#DbRepattr">attribute</a> "sqlResultFieldSep". <br><br>
  8085. The module provides a command history once a sqlCmd command was executed successfully.
  8086. To use this option, activate the attribute "sqlCmdHistoryLength" with list lenght you want. <br><br>
  8087. For a better overview the relevant attributes for sqlCmd are listed in a table: <br><br>
  8088. <ul>
  8089. <table>
  8090. <colgroup> <col width=5%> <col width=95%> </colgroup>
  8091. <tr><td> <b>allowDeletion</b> </td><td>: activates capabilty to delete datasets </td></tr>
  8092. <tr><td> <b>sqlResultFormat</b> </td><td>: determines presentation style of command result </td></tr>
  8093. <tr><td> <b>sqlResultFieldSep</b> </td><td>: choice of a useful field separator for result </td></tr>
  8094. <tr><td> <b>sqlCmdHistoryLength</b> </td><td>: activates command history and length </td></tr>
  8095. </table>
  8096. </ul>
  8097. <br>
  8098. <br>
  8099. <b>Note:</b> <br>
  8100. Even though the module works non-blocking regarding to database operations, a huge
  8101. sample space (number of rows/readings) could block the browser session respectively
  8102. FHEMWEB.
  8103. If you are unsure about the result of the statement, you should preventively add a limit to
  8104. the statement. <br><br>
  8105. </li><br>
  8106. </ul>
  8107. <li><b> sqlCmdHistory </b> - If history is activated by <a href="#DbRepattr">attribute</a> "sqlCmdHistoryLength", an already
  8108. successfully executed sqlCmd-command can be repeated from a drop-down list. <br>
  8109. By execution of the last list entry, "__purge_historylist__", the list itself can be deleted. <br>
  8110. If the statement contains "," this character is displayed as "&lt;c&gt;" in the history
  8111. list due to technical restrictions. <br>
  8112. </li><br>
  8113. <li><b> sumValue [display | writeToDB]</b>
  8114. - calculates the summary of database column "VALUE" between period given by
  8115. <a href="#DbRepattr">attributes</a> "timestamp_begin", "timestamp_end" or
  8116. "timeDiffToNow / timeOlderThan". The reading to evaluate must be defined using attribute
  8117. "reading". Using this function is mostly reasonable if value-differences of readings
  8118. are written to the database. <br>
  8119. Is no or the option "display" specified, the results are only displayed. Using
  8120. option "writeToDB" the calculation results are stored in the database with a new reading
  8121. name. <br>
  8122. The new readingname is built of a prefix and the original reading name.
  8123. The prefix is made up of the creation function and the aggregation. <br>
  8124. The timestamp of the new stored readings is deviated from aggregation period,
  8125. unless no unique point of time of the result can be determined.
  8126. The field "EVENT" will be filled with "calculated".<br><br>
  8127. <ul>
  8128. <b>Example of building a new reading name from the original reading "totalpac":</b> <br>
  8129. sum_day_totalpac <br>
  8130. # &lt;creation function&gt;_&lt;aggregation&gt;_&lt;original reading&gt; <br>
  8131. </li> <br>
  8132. </ul>
  8133. <br>
  8134. <li><b> syncStandby &lt;DbLog-Device Standby&gt; </b>
  8135. - datasets of the connected database (source) are transmitted into another database
  8136. (Standby-database). <br>
  8137. Here the "&lt;DbLog-Device Standby&gt;" is the DbLog-Device what is connected to the
  8138. Standby-database. <br><br>
  8139. All the datasets which are determined by timestamp-<a href="#DbRepattrlimit">attributes</a>
  8140. or respectively the attributes "device", "reading" are transmitted. <br>
  8141. The datasets are transmitted in time slices accordingly to the adjusted aggregation.
  8142. If the attribute "aggregation" has value "no" or "month", the datasets are transmitted
  8143. automatically in daily time slices into standby-database.
  8144. Source- and Standby-database can be of different types.
  8145. <br><br>
  8146. The relevant attributes to control the syncStandby function are: <br><br>
  8147. <ul>
  8148. <table>
  8149. <colgroup> <col width=5%> <col width=95%> </colgroup>
  8150. <tr><td> <b>aggregation</b> </td><td>: adjustment of time slices for data transmission (hour,day,week) </td></tr>
  8151. <tr><td> <b>device</b> </td><td>: transmit only datasets which are contain &lt;device&gt; </td></tr>
  8152. <tr><td> <b>reading</b> </td><td>: transmit only datasets which are contain &lt;reading&gt; </td></tr>
  8153. <tr><td> <b>time.*</b> </td><td>: A number of attributes to limit selection by time </td></tr>
  8154. </table>
  8155. </ul>
  8156. <br>
  8157. <br>
  8158. </li> <br>
  8159. <li><b> tableCurrentFillup </b> - the current-table will be filled u with an extract of the history-table.
  8160. The <a href="#DbRepattr">attributes</a> for limiting time and device, reading are considered.
  8161. Thereby the content of the extract can be affected. In the associated DbLog-device the attribute "DbLogType" should be set to
  8162. "SampleFill/History". </li> <br>
  8163. <li><b> tableCurrentPurge </b> - deletes the content of current-table. There are no limits, e.g. by attributes "timestamp_begin", "timestamp_end", device, reading
  8164. and so on, considered. </li> <br>
  8165. <li><b> vacuum </b> - optimize tables in the connected database (SQLite, PostgreSQL). <br>
  8166. Before and after an optimization it is possible to execute a FHEM command.
  8167. (please see <a href="#DbRepattr">attributes</a> "executeBeforeProc", "executeAfterProc")
  8168. <br><br>
  8169. <ul>
  8170. <b>Note:</b> <br>
  8171. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  8172. is operating in asynchronous mode to avoid FHEM from blocking. <br><br>
  8173. </li>
  8174. </ul><br>
  8175. <br>
  8176. </ul></ul>
  8177. <b>For all evaluation variants (except sqlCmd,deviceRename,readingRename) applies: </b> <br>
  8178. In addition to the needed reading the device can be complemented to restrict the datasets for reporting / function.
  8179. If no time limit attribute is set but aggregation is set, the period from the oldest dataset in database to the current
  8180. date/time will be used as selection criterion. If the oldest dataset wasn't identified, then '1970-01-01 01:00:00' is used
  8181. as start date (see get &lt;name&gt; "minTimestamp" also).
  8182. If both time limit attribute and aggregation isn't set, the selection on database is runnung without timestamp criterion.
  8183. <br><br>
  8184. <b>Note: </b> <br>
  8185. 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.
  8186. <br><br>
  8187. </ul>
  8188. <a name="DbRepget"></a>
  8189. <b>Get </b>
  8190. <ul>
  8191. The get-commands of DbRep provide to retrieve some metadata of the used database instance.
  8192. Those are for example adjusted server parameter, server variables, datadasestatus- and table informations. THe available get-functions depending of
  8193. the used database type. So for SQLite curently only "get svrinfo" is usable. The functions nativ are delivering a lot of outpit values.
  8194. They can be limited by function specific <a href="#DbRepattr">attributes</a>. The filter has to be setup by a comma separated list.
  8195. SQL-Wildcard (%) can be used to setup the list arguments.
  8196. <br><br>
  8197. <b>Note: </b> <br>
  8198. After executing a get-funktion in detail view please make a browser refresh to see the results !
  8199. <br><br>
  8200. <ul><ul>
  8201. <li><b> blockinginfo </b> - list the current system wide running background processes (BlockingCalls) together with their informations.
  8202. If character string is too long (e.g. arguments) it is reported shortened.
  8203. </li>
  8204. <br><br>
  8205. <li><b> dbstatus </b> - lists global informations about MySQL server status (e.g. informations related to cache, threads, bufferpools, etc. ).
  8206. Initially all available informations are reported. Using the <a href="#DbRepattr">attribute</a> "showStatus" the quantity of
  8207. results can be limited to show only the desired values. Further detailed informations of items meaning are
  8208. explained <a href=http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html>there</a>. <br>
  8209. <br><ul>
  8210. <b>Example</b> <br>
  8211. get &lt;name&gt; dbstatus <br>
  8212. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  8213. # Only readings containing "uptime" and "qcache" in name will be created
  8214. </li>
  8215. <br><br>
  8216. </ul>
  8217. <li><b> dbvars </b> - lists global informations about MySQL system variables. Included are e.g. readings related to InnoDB-Home, datafile path,
  8218. memory- or cache-parameter and so on. The Output reports initially all available informations. Using the
  8219. <a href="#DbRepattr">attribute</a> "showVariables" the quantity of results can be limited to show only the desired values.
  8220. Further detailed informations of items meaning are explained
  8221. <a href=http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html>there</a>. <br>
  8222. <br><ul>
  8223. <b>Example</b> <br>
  8224. get &lt;name&gt; dbvars <br>
  8225. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  8226. # Only readings containing "version" and "query_cache" in name will be created
  8227. </li>
  8228. <br><br>
  8229. </ul>
  8230. <li><b> minTimestamp </b> - Identifies the oldest timestamp in the database (will be executed implicitely at FHEM start).
  8231. The timestamp is used as begin of data selection if no time attribut is set to determine the
  8232. start date.
  8233. </li>
  8234. <br><br>
  8235. <li><b> procinfo </b> - reports the existing database processes in a summary table (only MySQL). <br>
  8236. Typically only the own processes of the connection user (set in DbLog configuration file) will be
  8237. reported. If all precesses have to be reported, the global "PROCESS" right has to be granted to the
  8238. user. <br>
  8239. As of MariaDB 5.3 for particular SQL-Statements a progress reporting will be provided
  8240. (table row "PROGRESS"). So you can track, for instance, the degree of processing during an index
  8241. creation. <br>
  8242. Further informations can be found
  8243. <a href=https://mariadb.com/kb/en/mariadb/show-processlist/>there</a>. <br>
  8244. </li>
  8245. <br><br>
  8246. <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
  8247. on the database type. Using the <a href="#DbRepattr">attribute</a> "showSvrInfo" the quantity of results can be limited to show only
  8248. the desired values. Further detailed informations of items meaning are explained
  8249. <a href=https://msdn.microsoft.com/en-us/library/ms711681(v=vs.85).aspx>there</a>. <br>
  8250. <br><ul>
  8251. <b>Example</b> <br>
  8252. get &lt;name&gt; svrinfo <br>
  8253. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  8254. # Only readings containing "SQL_CATALOG_TERM" and "NAME" in name will be created
  8255. </li>
  8256. <br><br>
  8257. </ul>
  8258. <li><b> tableinfo </b> - access detailed informations about tables in MySQL database which is connected by the DbRep-device.
  8259. All available tables in the connected database will be selected by default.
  8260. Using the<a href="#DbRepattr">attribute</a> "showTableInfo" the results can be limited to tables you want to show.
  8261. 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>
  8262. <br><ul>
  8263. <b>Example</b> <br>
  8264. get &lt;name&gt; tableinfo <br>
  8265. attr &lt;name&gt; showTableInfo current,history <br>
  8266. # Only informations related to tables "current" and "history" are going to be created
  8267. </li>
  8268. <br><br>
  8269. </ul>
  8270. <br>
  8271. </ul></ul>
  8272. </ul>
  8273. <a name="DbRepattr"></a>
  8274. <b>Attributes</b>
  8275. <br>
  8276. <ul>
  8277. Using the module specific attributes you are able to define the scope of evaluation and the aggregation. <br><br>
  8278. <b>Note for SQL-Wildcard Usage:</b> <br>
  8279. Within the attribute values of "device" and "reading" you may use SQL-Wildcard "%", Character "_" is not supported as a wildcard.
  8280. The character "%" stands for any characters. <br>
  8281. This rule is valid to all functions <b>except</b> "insert", "importFromFile" and "deviceRename". <br>
  8282. The function "insert" doesn't allow setting the mentioned attributes containing the wildcard "%". <br>
  8283. In readings the wildcard character "%" will be replaced by "/" to meet the rules of allowed characters in readings.
  8284. <br><br>
  8285. <ul><ul>
  8286. <li><b>aggregation </b> - Aggregation of Device/Reading-selections. Possible is hour, day, week, month or "no".
  8287. Delivers e.g. the count of database entries for a day (countEntries), Summation of
  8288. difference values of a reading (sumValue) and so on. Using aggregation "no" (default) an
  8289. aggregation don't happens but the output contaims all values of Device/Reading in the defined time period. </li> <br>
  8290. <li><b>allowDeletion </b> - unlocks the delete-function </li> <br>
  8291. <li><b>averageCalcForm </b> - specifies the calculation variant of average peak by "averageValue".
  8292. </li><br>
  8293. <li><b>device </b> - Selection of a particular device. <br>
  8294. You can specify <a href="https://fhem.de/commandref.html#devspec">device specifications (devspec)</a>. <br>
  8295. Inside of device specifications a SQL wildcard (%) will be evaluated as a normal ASCII-character.
  8296. The device names are derived from device specification and the active devices in FHEM before
  8297. SQL selection will be carried out. </li> <br>
  8298. <ul>
  8299. <b>Examples:</b> <br>
  8300. <code>attr &lt;name&gt; device TYPE=DbRep</code> <br>
  8301. # select datasets of active present devices with Type "DbRep" <br>
  8302. <code>attr &lt;name&gt; device MySTP_5000</code> <br>
  8303. # select datasets of device "MySTP_5000" <br>
  8304. <code>attr &lt;name&gt; device SMA.*</code> <br>
  8305. # select datasets of devices starting with "SMA" <br>
  8306. <code>attr &lt;name&gt; device SMA_Energymeter,MySTP_5000</code> <br>
  8307. # select datasets of devices "SMA_Energymeter" and "MySTP_5000" <br>
  8308. <code>attr &lt;name&gt; device %5000</code> <br>
  8309. # select datasets of devices ending with "5000" <br>
  8310. </ul>
  8311. <br><br>
  8312. <li><b>diffAccept </b> - valid for function diffValue. diffAccept determines the threshold, up to that a calaculated difference between two
  8313. straight sequently datasets should be commenly accepted (default = 20). <br>
  8314. Hence faulty DB entries with a disproportional high difference value will be eliminated and don't tamper the result.
  8315. If a threshold overrun happens, the reading "diff_overrun_limit_&lt;diffLimit&gt;" will be generated
  8316. (&lt;diffLimit&gt; will be substituted with the present prest attribute value). <br>
  8317. The reading contains a list of relevant pair of values. Using verbose=3 this list will also be reported in the FHEM
  8318. logfile.
  8319. </li><br>
  8320. <ul>
  8321. Example report in logfile if threshold of diffAccept=10 overruns: <br><br>
  8322. DbRep Rep.STP5000.etotal -> data ignored while calc diffValue due to threshold overrun (diffAccept = 10): <br>
  8323. 2016-04-09 08:50:50 0.0340 -> 2016-04-09 12:42:01 13.3440 <br><br>
  8324. # The first dataset with a value of 0.0340 is untypical low compared to the next value of 13.3440 and results a untypical
  8325. high difference value. <br>
  8326. # Now you have to decide if the (second) dataset should be deleted, ignored of the attribute diffAccept should be adjusted.
  8327. </ul><br>
  8328. <li><b>disable </b> - deactivates the module </li> <br>
  8329. <li><b>dumpComment </b> - User-comment. It will be included in the header of the created dumpfile by
  8330. command "dumpMySQL clientSide". </li> <br>
  8331. <li><b>dumpCompress </b> - if set, the dump files are compressed after operation of "dumpMySQL" bzw. "dumpSQLite" </li> <br>
  8332. <li><b>dumpDirLocal </b> - Target directory of database dumps by command "dumpMySQL clientSide"
  8333. (default: "{global}{modpath}/log/" on the FHEM-Server). <br>
  8334. In this directory also the internal version administration searches for old backup-files
  8335. and deletes them if the number exceeds attribute "dumpFilesKeep".
  8336. The attribute is also relevant to publish a local mounted directory "dumpDirRemote" to
  8337. DbRep. </li> <br>
  8338. <li><b>dumpDirRemote </b> - Target directory of database dumps by command "dumpMySQL serverSide"
  8339. (default: the Home-directory of MySQL-Server on the MySQL-Host). </li> <br>
  8340. <li><b>dumpMemlimit </b> - tolerable memory consumption for the SQL-script during generation period (default: 100000 characters).
  8341. Please adjust this parameter if you may notice memory bottlenecks and performance problems based
  8342. on it on your specific hardware. </li> <br>
  8343. <li><b>dumpSpeed </b> - Number of Lines which will be selected in source database with one select by dump-command
  8344. "dumpMySQL ClientSide" (default: 10000).
  8345. This parameter impacts the run-time and consumption of resources directly. </li> <br>
  8346. <li><b>dumpFilesKeep </b> - The specified number of dumpfiles remain in the dump directory (default: 3).
  8347. If there more (older) files has been found, these files will be deleted after a new database dump
  8348. was created successfully.
  8349. The global attrubute "archivesort" will be considered. </li> <br>
  8350. <li><b>executeAfterProc </b> - you can specify a FHEM-command which should be executed <b>after dump</b>. <br>
  8351. Perl functions have to be enclosed in {} .<br><br>
  8352. <ul>
  8353. <b>Example:</b> <br><br>
  8354. attr &lt;name&gt; executeAfterProc set og_gz_westfenster off; <br>
  8355. attr &lt;name&gt; executeAfterProc {adump ("&lt;name&gt;")} <br><br>
  8356. # "adump" is a function defined in 99_myUtils.pm e.g.: <br>
  8357. <pre>
  8358. sub adump {
  8359. my ($name) = @_;
  8360. my $hash = $defs{$name};
  8361. # own function, e.g.
  8362. Log3($name, 3, "DbRep $name -> Dump finished");
  8363. return;
  8364. }
  8365. </pre>
  8366. </ul>
  8367. </li>
  8368. <li><b>executeBeforeProc </b> - you can specify a FHEM-command which should be executed <b>before dump</b>. <br>
  8369. Perl functions have to be enclosed in {} .<br><br>
  8370. <ul>
  8371. <b>Example:</b> <br><br>
  8372. attr &lt;name&gt; executeBeforeProc set og_gz_westfenster on; <br>
  8373. attr &lt;name&gt; executeBeforeProc {bdump ("&lt;name&gt;")} <br><br>
  8374. # "bdump" is a function defined in 99_myUtils.pm e.g.: <br>
  8375. <pre>
  8376. sub bdump {
  8377. my ($name) = @_;
  8378. my $hash = $defs{$name};
  8379. # own function, e.g.
  8380. Log3($name, 3, "DbRep $name -> Dump starts now");
  8381. return;
  8382. }
  8383. </pre>
  8384. </ul>
  8385. </li>
  8386. <li><b>expimpfile </b> - Path/filename for data export/import </li> <br>
  8387. <li><b>fetchMarkDuplicates </b>
  8388. - Highlighting of multiple occuring datasets in result of "fetchrows" command </li> <br>
  8389. <li><b>fetchRoute [descent | ascent] </b> - specify the direction of data selection of the fetchrows-command. <br><br>
  8390. <ul>
  8391. <b>descent</b> - the data are read descent (default). If
  8392. amount of datasets specified by attribut "limit" is exceeded,
  8393. the newest x datasets are shown. <br><br>
  8394. <b>ascent</b> - the data are read ascent . If
  8395. amount of datasets specified by attribut "limit" is exceeded,
  8396. the oldest x datasets are shown. <br>
  8397. </ul>
  8398. </li> <br><br>
  8399. <li><b>ftpUse </b> - FTP Transfer after dump will be switched on (without SSL encoding). The created
  8400. database backup file will be transfered non-blocking to the FTP-Server (Attribut "ftpServer").
  8401. </li> <br>
  8402. <li><b>ftpUseSSL </b> - FTP Transfer with SSL encoding after dump. The created database backup file will be transfered
  8403. non-blocking to the FTP-Server (Attribut "ftpServer"). </li> <br>
  8404. <li><b>ftpUser </b> - User for FTP-server login, default: "anonymous". </li> <br>
  8405. <li><b>ftpDebug </b> - debugging of FTP communication for diagnostics. </li> <br>
  8406. <li><b>ftpDir </b> - directory on FTP-server in which the file will be send into (default: "/"). </li> <br>
  8407. <li><b>ftpDumpFilesKeep </b> - leave the number of dump files in FTP-destination &lt;ftpDir&gt; (default: 3). Are there more
  8408. (older) dump files present, these files are deleted after a new dump was transfered successfully. </li> <br>
  8409. <li><b>ftpPassive </b> - set if passive FTP is to be used </li> <br>
  8410. <li><b>ftpPort </b> - FTP-Port, default: 21 </li> <br>
  8411. <li><b>ftpPwd </b> - password of FTP-User, is not set by default </li> <br>
  8412. <li><b>ftpServer </b> - name or IP-address of FTP-server. <b>absolutely essential !</b> </li> <br>
  8413. <li><b>ftpTimeout </b> - timeout of FTP-connection in seconds (default: 30). </li> <br>
  8414. <a name="DbRepattrlimit"></a>
  8415. <li><b>limit </b> - limits the number of selected datasets by the "fetchrows", or the shown datasets of "delSeqDoublets adviceDelete",
  8416. "delSeqDoublets adviceRemain" commands (default: 1000).
  8417. This limitation should prevent the browser session from overload and
  8418. avoids FHEMWEB from blocking. Please change the attribut according your requirements or change the
  8419. selection criteria (decrease evaluation period). </li> <br>
  8420. <li><b>optimizeTablesBeforeDump </b> - if set to "1", the database tables will be optimized before executing the dump
  8421. (default: 0).
  8422. Thereby the backup run-time time will be extended. <br><br>
  8423. <ul>
  8424. <b>Note</b> <br>
  8425. The table optimizing cause locking the tables and therefore to blocking of
  8426. FHEM if DbLog isn't working in asynchronous mode (DbLog-attribute "asyncMode") !
  8427. <br>
  8428. </ul>
  8429. </li> <br>
  8430. <li><b>reading </b> - Selection of a particular reading.
  8431. More than one reading are specified as a comma separated list. <br>
  8432. If SQL wildcard (%) is set in a list, it will be evaluated as a normal ASCII-character. <br>
  8433. </li> <br>
  8434. <ul>
  8435. <b>Examples:</b> <br>
  8436. <code>attr &lt;name&gt; reading etotal</code> <br>
  8437. <code>attr &lt;name&gt; reading et%</code> <br>
  8438. <code>attr &lt;name&gt; reading etotal,etoday</code> <br>
  8439. </ul>
  8440. <br><br>
  8441. <li><b>readingNameMap </b> - the name of the analyzed reading can be overwritten for output </li> <br>
  8442. <li><b>role </b> - the role of the DbRep-device. Standard role is "Client". The role "Agent" is described
  8443. in section <a href="#DbRepAutoRename">DbRep-Agent</a>. </li> <br>
  8444. <li><b>readingPreventFromDel </b> - comma separated list of readings which are should prevent from deletion when a
  8445. new operation starts </li> <br>
  8446. <li><b>seqDoubletsVariance </b> - accepted variance (+/-) for the command "set &lt;name&gt; delSeqDoublets". <br>
  8447. The value of this attribute describes the variance up to it consecutive numeric values (VALUE) of
  8448. datasets are handled as identical and should be deleted. "seqDoubletsVariance" is an absolut numerical value,
  8449. which is used as a positive as well as a negative variance. </li> <br>
  8450. <ul>
  8451. <b>Examples:</b> <br>
  8452. <code>attr &lt;name&gt; seqDoubletsVariance 0.0014 </code> <br>
  8453. <code>attr &lt;name&gt; seqDoubletsVariance 1.45 </code> <br>
  8454. </ul>
  8455. <br><br>
  8456. <li><b>showproctime </b> - if set, the reading "sql_processing_time" shows the required execution time (in seconds)
  8457. for the sql-requests. This is not calculated for a single sql-statement, but the summary
  8458. of all sql-statements necessara for within an executed DbRep-function in background. </li> <br>
  8459. <li><b>showStatus </b> - limits the sample space of command "get &lt;name&gt; dbstatus". SQL-Wildcard (%) can be used. </li> <br>
  8460. <ul>
  8461. <b>Example: </b><br>
  8462. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  8463. # Only readings with containing "uptime" and "qcache" in name will be shown <br>
  8464. </ul><br>
  8465. <li><b>showVariables </b> - limits the sample space of command "get &lt;name&gt; dbvars". SQL-Wildcard (%) can be used. </li> <br>
  8466. <ul>
  8467. <b>Example: </b><br>
  8468. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  8469. # Only readings with containing "version" and "query_cache" in name will be shown <br>
  8470. </ul><br>
  8471. <li><b>showSvrInfo </b> - limits the sample space of command "get &lt;name&gt; svrinfo". SQL-Wildcard (%) can be used. </li> <br>
  8472. <ul>
  8473. <b>Example: </b><br>
  8474. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  8475. # Only readings with containing "SQL_CATALOG_TERM" and "NAME" in name will be shown <br>
  8476. </ul><br>
  8477. <li><b>showTableInfo </b> - limits the tablename which is selected by command "get &lt;name&gt; tableinfo". SQL-Wildcard
  8478. (%) can be used. </li> <br>
  8479. <ul>
  8480. <b>Example: </b><br>
  8481. attr &lt;name&gt; showTableInfo current,history <br>
  8482. # Only informations about tables "current" and "history" will be shown <br>
  8483. </ul><br>
  8484. <li><b>sqlCmdHistoryLength </b>
  8485. - activates the command history of "sqlCmd" and determines the length of it </li> <br>
  8486. <li><b>sqlResultFieldSep </b> - determines the used field separator (default: "|") in the result of command "set &lt;name&gt; sqlCmd". </li> <br>
  8487. <li><b>sqlResultFormat </b> - determines the formatting of the "set &lt;name&gt; sqlCmd" command result.
  8488. Possible options are: <br><br>
  8489. <ul>
  8490. <b>separated </b> - every line of the result will be generated sequentially in a single
  8491. reading. (default) <br><br>
  8492. <b>mline </b> - the result will be generated as multiline in
  8493. <a href="#DbRepReadings">Reading</a> SqlResult.
  8494. <br><br>
  8495. <b>sline </b> - the result will be generated as singleline in
  8496. <a href="#DbRepReadings">Reading</a> SqlResult.
  8497. Datasets are separated by "]|[". <br><br>
  8498. <b>table </b> - the result will be generated as an table in
  8499. <a href="#DbRepReadings">Reading</a> SqlResult. <br><br>
  8500. <b>json </b> - creates <a href="#DbRepReadings">Reading</a> SqlResult as a JSON
  8501. coded hash.
  8502. Every hash-element consists of the serial number of the dataset (key)
  8503. and its value. </li> <br><br>
  8504. To process the result, you may use a userExitFn in 99_myUtils for example: <br>
  8505. <pre>
  8506. sub resfromjson {
  8507. my ($name,$reading,$value) = @_;
  8508. my $hash = $defs{$name};
  8509. if ($reading eq "SqlResult") {
  8510. # only reading SqlResult contains JSON encoded data
  8511. my $data = decode_json($value);
  8512. foreach my $k (keys(%$data)) {
  8513. # use your own processing from here for every hash-element
  8514. # e.g. output of every element that contains "Cam"
  8515. my $ke = $data->{$k};
  8516. if($ke =~ m/Cam/i) {
  8517. my ($res1,$res2) = split("\\|", $ke);
  8518. Log3($name, 1, "$name - extract element $k by userExitFn: ".$res1." ".$res2);
  8519. }
  8520. }
  8521. }
  8522. return;
  8523. }
  8524. </pre>
  8525. </ul>
  8526. <br>
  8527. <li><b>timeYearPeriod </b> - By this attribute an annual time period will be determined for database data selection.
  8528. The time limits are calculated dynamically during execution time. Every time an annual period is determined.
  8529. Periods of less than a year are not possible to set. <br>
  8530. This attribute is particularly intended to make reports synchronous to an account period, e.g. of an energy- or gas provider.
  8531. </li> <br>
  8532. <ul>
  8533. <b>Example:</b> <br><br>
  8534. attr &lt;name&gt; timeYearPeriod 06-25 06-24 <br><br>
  8535. # evaluates the database within the time limits 25. june AAAA and 24. june BBBB. <br>
  8536. # The year AAAA respectively year BBBB is calculated dynamically depending of the current date. <br>
  8537. # If the current date >= 25. june and =< 31. december, than AAAA = current year and BBBB = current year+1 <br>
  8538. # If the current date >= 01. january und =< 24. june, than AAAA = current year-1 and BBBB = current year
  8539. </ul>
  8540. <br><br>
  8541. <li><b>timestamp_begin </b> - begin of data selection (*) </li> <br>
  8542. <li><b>timestamp_end </b> - end of data selection. If not set the current date/time combination will be used. (*) </li> <br>
  8543. (*) The format of timestamp is as used with DbLog "YYYY-MM-DD HH:MM:SS". For the attributes "timestamp_begin", "timestamp_end"
  8544. you can also use one of the following entries. The timestamp-attribute will be dynamically set to: <br><br>
  8545. <ul>
  8546. <b>current_year_begin</b> : matches "&lt;current year&gt;-01-01 00:00:00" <br>
  8547. <b>current_year_end</b> : matches "&lt;current year&gt;-12-31 23:59:59" <br>
  8548. <b>previous_year_begin</b> : matches "&lt;previous year&gt;-01-01 00:00:00" <br>
  8549. <b>previous_year_end</b> : matches "&lt;previous year&gt;-12-31 23:59:59" <br>
  8550. <b>current_month_begin</b> : matches "&lt;current month first day&gt; 00:00:00" <br>
  8551. <b>current_month_end</b> : matches "&lt;current month last day&gt; 23:59:59" <br>
  8552. <b>previous_month_begin</b> : matches "&lt;previous month first day&gt; 00:00:00" <br>
  8553. <b>previous_month_end</b> : matches "&lt;previous month last day&gt; 23:59:59" <br>
  8554. <b>current_week_begin</b> : matches "&lt;first day of current week&gt; 00:00:00" <br>
  8555. <b>current_week_end</b> : matches "&lt;last day of current week&gt; 23:59:59" <br>
  8556. <b>previous_week_begin</b> : matches "&lt;first day of previous week&gt; 00:00:00" <br>
  8557. <b>previous_week_end</b> : matches "&lt;last day of previous week&gt; 23:59:59" <br>
  8558. <b>current_day_begin</b> : matches "&lt;current day&gt; 00:00:00" <br>
  8559. <b>current_day_end</b> : matches "&lt;current day&gt; 23:59:59" <br>
  8560. <b>previous_day_begin</b> : matches "&lt;previous day&gt; 00:00:00" <br>
  8561. <b>previous_day_end</b> : matches "&lt;previous day&gt; 23:59:59" <br>
  8562. <b>current_hour_begin</b> : matches "&lt;current hour&gt;:00:00" <br>
  8563. <b>current_hour_end</b> : matches "&lt;current hour&gt;:59:59" <br>
  8564. <b>previous_hour_begin</b> : matches "&lt;previous hour&gt;:00:00" <br>
  8565. <b>previous_hour_end</b> : matches "&lt;previous hour&gt;:59:59" <br> </ul><br>
  8566. Make sure that "timestamp_begin" < "timestamp_end" is fulfilled. <br><br>
  8567. <ul>
  8568. <b>Example:</b> <br><br>
  8569. attr &lt;name&gt; timestamp_begin current_year_begin <br>
  8570. attr &lt;name&gt; timestamp_end current_year_end <br><br>
  8571. # Analyzes the database between the time limits of the current year. <br>
  8572. </ul>
  8573. <br><br>
  8574. <b>Note </b> <br>
  8575. If the attribute "timeDiffToNow" will be set, the attributes "timestamp_begin" respectively "timestamp_end" will be deleted if they were set before.
  8576. The setting of "timestamp_begin" respectively "timestamp_end" causes the deletion of attribute "timeDiffToNow" if it was set before as well.
  8577. <br><br>
  8578. <li><b>timeDiffToNow </b> - the <b>begin</b> of data selection will be set to the timestamp <b>"&lt;current time&gt; -
  8579. &lt;timeDiffToNow&gt;"</b> dynamically (e.g. if set to 86400, the last 24 hours are considered by data
  8580. selection). The time period will be calculated dynamically at execution time. </li> <br>
  8581. <ul>
  8582. <b>Examples for input format:</b> <br>
  8583. <code>attr &lt;name&gt; timeDiffToNow 86400</code> <br>
  8584. # the start time is set to "current time - 86400 seconds" <br>
  8585. <code>attr &lt;name&gt; timeDiffToNow d:2 h:3 m:2 s:10</code> <br>
  8586. # the start time is set to "current time - 2 days 3 hours 2 minutes 10 seconds" <br>
  8587. <code>attr &lt;name&gt; timeDiffToNow m:600</code> <br>
  8588. # the start time is set to "current time - 600 minutes" gesetzt <br>
  8589. <code>attr &lt;name&gt; timeDiffToNow h:2.5</code> <br>
  8590. # the start time is set to "current time - 2,5 hours" <br>
  8591. </ul>
  8592. <br><br>
  8593. <li><b>timeOlderThan </b> - the <b>end</b> of data selection will be set to the timestamp <b>"&lt;aktuelle Zeit&gt; -
  8594. &lt;timeOlderThan&gt;"</b> dynamically. Always the datasets up to timestamp
  8595. "&lt;current time&gt; - &lt;timeOlderThan&gt;" will be considered (e.g. if set to
  8596. 86400, all datasets older than one day are considered). The time period will be calculated dynamically at
  8597. execution time. <br>
  8598. The valid input format for attribute "timeOlderThan" is identical to attribute "timeDiffToNow". </li> <br>
  8599. <li><b>timeout </b> - set the timeout-value for Blocking-Call Routines in background in seconds (default 86400) </li> <br>
  8600. <li><b>userExitFn </b> - provides an interface to execute user specific program code. <br>
  8601. To activate the interfaace at first you should implement the subroutine which will be
  8602. called by the interface in your 99_myUtls.pm as shown in by the example: <br>
  8603. <pre>
  8604. sub UserFunction {
  8605. my ($name,$reading,$value) = @_;
  8606. my $hash = $defs{$name};
  8607. ...
  8608. # e.g. output transfered data
  8609. Log3 $name, 1, "UserExitFn $name called - transfer parameter are Reading: $reading, Value: $value " ;
  8610. ...
  8611. return;
  8612. }
  8613. </pre>
  8614. The interface activation takes place by setting the subroutine name into the attribute.
  8615. Optional you may set a Reading:Value combination (Regex) as argument. If no Regex is
  8616. specified, all value combinations will be evaluated as "true" (related to .*:.*).
  8617. <br><br>
  8618. <ul>
  8619. <b>Example:</b> <br>
  8620. attr <device> userExitFn UserFunction .*:.* <br>
  8621. # "UserFunction" is the name of subroutine in 99_myUtils.pm.
  8622. </ul>
  8623. <br>
  8624. The interface works generally without and independent from Events.
  8625. If the attribute is set, after every reading generation the Regex will be evaluated.
  8626. If the evaluation was "true", set subroutine will be called.
  8627. For further processing following parameters will be forwarded to the function: <br><br>
  8628. <ul>
  8629. <li>$name - the name of the DbRep-Device </li>
  8630. <li>$reading - the name of the created reading </li>
  8631. <li>$value - the value of the reading </li>
  8632. </ul>
  8633. </li>
  8634. <br><br>
  8635. </ul></ul></ul>
  8636. <a name="DbRepReadings"></a>
  8637. <b>Readings</b>
  8638. <br>
  8639. <ul>
  8640. 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
  8641. that unsuitable or invalid readings would remain.<br><br>
  8642. In addition the following readings will be created: <br><br>
  8643. <ul><ul>
  8644. <li><b>state </b> - contains the current state of evaluation. If warnings are occured (state = Warning) compare Readings
  8645. "diff_overrun_limit_&lt;diffLimit&gt;" and "less_data_in_period" </li> <br>
  8646. <li><b>errortext </b> - description about the reason of an error state </li> <br>
  8647. <li><b>background_processing_time </b> - the processing time spent for operations in background/forked operation </li> <br>
  8648. <li><b>sql_processing_time </b> - the processing time wasted for all sql-statements used for an operation </li> <br>
  8649. <li><b>diff_overrun_limit_&lt;diffLimit&gt;</b> - contains a list of pairs of datasets which have overrun the threshold (&lt;diffLimit&gt;)
  8650. of calculated difference each other determined by attribute "diffAccept" (default=20). </li> <br>
  8651. <li><b>less_data_in_period </b> - contains a list of time periods within only one dataset was found. The difference calculation considers
  8652. the last value of the aggregation period before the current one. Valid for function "diffValue". </li> <br>
  8653. <li><b>SqlResult </b> - result of the last executed sqlCmd-command. The formatting can be specified
  8654. by <a href="#DbRepattr">attribute</a> "sqlResultFormat" </li> <br>
  8655. <li><b>sqlCmd </b> - contains the last executed sqlCmd-command </li> <br>
  8656. </ul></ul>
  8657. <br><br>
  8658. </ul>
  8659. <a name="DbRepAutoRename"></a>
  8660. <b>DbRep Agent - automatic change of device names in databases and DbRep-definitions after FHEM "rename" command</b>
  8661. <br>
  8662. <ul>
  8663. 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
  8664. react automatically on renaming devices in your FHEM installation. The DbRep device is now called DbRep-Agent. <br><br>
  8665. By the DbRep-Agent the following features are activated when a FHEM-device has being renamed: <br><br>
  8666. <ul><ul>
  8667. <li> in the database connected to the DbRep-Agent (Internal Database) dataset containing the old device name will be searched and renamed to the
  8668. to the new device name in <b>all</b> affected datasets. </li> <br>
  8669. <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
  8670. the renamed device will be going on in the database. </li> <br>
  8671. <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".
  8672. Because of that, reporting definitions will be kept consistent automatically if devices are renamed in FHEM. </li> <br>
  8673. </ul></ul>
  8674. 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
  8675. and checked: <br><br>
  8676. <ul><ul>
  8677. <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,
  8678. you could define same numbers of DbRep-Agents as well as DbLog-devices are defined. </li> <br>
  8679. <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
  8680. permitted. If a DbRep-device of privious type "Client" has changed an Agent, furthermore not permitted attributes will be deleted if set. </li> <br>
  8681. </ul></ul>
  8682. 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
  8683. function don't running into timeout set the timeout attribute to an appropriate value, especially if there are databases with huge datasets to evaluate.
  8684. As well as all the other database operations of this module, the autorename operation will be executed nonblocking. <br><br>
  8685. <ul>
  8686. <b>Example </b> of definition of a DbRep-device as an Agent: <br><br>
  8687. <code>
  8688. define Rep.Agent DbRep LogDB <br>
  8689. attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen <br>
  8690. attr Rep.Agent icon security <br>
  8691. attr Rep.Agent role Agent <br>
  8692. attr Rep.Agent room DbLog <br>
  8693. attr Rep.Agent showproctime 1 <br>
  8694. 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>
  8695. attr Rep.Agent timeout 86400 <br>
  8696. </code>
  8697. <br>
  8698. </ul>
  8699. <b>Note:</b> <br>
  8700. Even though the function itself is designed non-blocking, make sure the assigned DbLog-device
  8701. is operating in asynchronous mode to avoid FHEMWEB from blocking. <br><br>
  8702. </ul>
  8703. =end html
  8704. =begin html_DE
  8705. <a name="DbRep"></a>
  8706. <h3>DbRep</h3>
  8707. <ul>
  8708. <br>
  8709. Zweck des Moduls ist es, den Inhalt von DbLog-Datenbanken nach bestimmten Kriterien zu durchsuchen, zu managen, das Ergebnis hinsichtlich verschiedener
  8710. Aggregationen auszuwerten und als Readings darzustellen. Die Abgrenzung der zu berücksichtigenden Datenbankinhalte erfolgt durch die Angabe von Device, Reading und
  8711. die Zeitgrenzen für Auswertungsbeginn bzw. Auswertungsende. <br><br>
  8712. Alle Datenbankoperationen werden nichtblockierend ausgeführt. Die Ausführungszeit der (SQL)-Hintergrundoperationen kann optional ebenfalls als Reading bereitgestellt
  8713. werden (siehe <a href="#DbRepattr">Attribute</a>). <br>
  8714. Alle vorhandenen Readings werden vor einer neuen Operation gelöscht. Durch das Attribut "readingPreventFromDel" kann eine Komma separierte Liste von Readings
  8715. angegeben werden die nicht gelöscht werden sollen. <br><br>
  8716. Aktuell werden folgende Operationen unterstützt: <br><br>
  8717. <ul><ul>
  8718. <li> Selektion aller Datensätze innerhalb einstellbarer Zeitgrenzen </li>
  8719. <li> Darstellung der Datensätze einer Device/Reading-Kombination innerhalb einstellbarer Zeitgrenzen. </li>
  8720. <li> Selektion der Datensätze unter Verwendung von dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt. </li>
  8721. <li> Dubletten-Hervorhebung bei Datensatzanzeige (fetchrows) </li>
  8722. <li> Berechnung der Anzahl von Datensätzen einer Device/Reading-Kombination unter Berücksichtigung von Zeitgrenzen
  8723. und verschiedenen Aggregationen. </li>
  8724. <li> Die Berechnung von Summen-, Differenz-, Maximum-, Minimum- und Durchschnittswerten numerischer Readings
  8725. in Zeitgrenzen und verschiedenen Aggregationen. </li>
  8726. <li> Speichern von Summen-, Differenz- , Maximum- , Minimum- und Durchschnittswertberechnungen in der Datenbank </li>
  8727. <li> Löschung von Datensätzen. Die Eingrenzung der Löschung kann durch Device und/oder Reading sowie fixer oder
  8728. dynamisch berechneter Zeitgrenzen zum Ausführungszeitpunkt erfolgen. </li>
  8729. <li> Export von Datensätzen in ein File im CSV-Format </li>
  8730. <li> Import von Datensätzen aus File im CSV-Format </li>
  8731. <li> Umbenennen von Device/Readings in Datenbanksätzen </li>
  8732. <li> Ändern von Reading-Werten (VALUES) in der Datenbank </li>
  8733. <li> automatisches Umbenennen von Device-Namen in Datenbanksätzen und DbRep-Definitionen nach FHEM "rename"
  8734. Befehl (siehe <a href="#DbRepAutoRename">DbRep-Agent</a>) </li>
  8735. <li> Ausführen von beliebigen Benutzer spezifischen SQL-Kommandos </li>
  8736. <li> Backups der FHEM-Datenbank im laufenden Betrieb erstellen (MySQL, SQLite) </li>
  8737. <li> senden des Dumpfiles zu einem FTP-Server nach dem Backup incl. Versionsverwaltung </li>
  8738. <li> Restore von SQLite-Dumps und MySQL serverSide-Backups </li>
  8739. <li> Optimierung der angeschlossenen Datenbank (optimizeTables, vacuum) </li>
  8740. <li> Ausgabe der existierenden Datenbankprozesse (MySQL) </li>
  8741. <li> leeren der current-Tabelle </li>
  8742. <li> Auffüllen der current-Tabelle mit einem (einstellbaren) Extrakt der history-Tabelle</li>
  8743. <li> Bereinigung sequentiell aufeinander folgender Datensätze mit unterschiedlichen Zeitstempel aber gleichen Werten (sequentielle Dublettenbereinigung) </li>
  8744. <li> Reparatur einer korrupten SQLite Datenbank ("database disk image is malformed") </li>
  8745. <li> Übertragung von Datensätzen aus der Quelldatenbank in eine andere (Standby) Datenbank (syncStandby) </li>
  8746. </ul></ul>
  8747. <br>
  8748. Zur Aktivierung der Funktion "Autorename" wird dem definierten DbRep-Device mit dem Attribut "role" die Rolle "Agent" zugewiesen. Die Standardrolle nach Definition
  8749. ist "Client". Mehr ist dazu im Abschnitt <a href="#DbRepAutoRename">DbRep-Agent</a> beschrieben. <br><br>
  8750. DbRep stellt dem Nutzer einen UserExit zur Verfügung. Über diese Schnittstelle kann der Nutzer in Abhängigkeit von
  8751. frei definierbaren Reading/Value-Kombinationen (Regex) eigenen Code zur Ausführung bringen. Diese Schnittstelle arbeitet
  8752. unabhängig von einer Eventgenerierung. Weitere Informationen dazu ist unter <a href="#DbRepattr">Attribut</a>
  8753. "userExitFn" beschrieben. <br><br>
  8754. FHEM-Forum: <br>
  8755. <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>
  8756. FHEM-Wiki: <br>
  8757. <a href="https://wiki.fhem.de/wiki/DbRep_-_Reporting_und_Management_von_DbLog-Datenbankinhalten">DbRep - Reporting und Management von DbLog-Datenbankinhalten</a>. <br><br>
  8758. <b>Voraussetzungen </b> <br><br>
  8759. Das Modul setzt den Einsatz einer oder mehrerer DbLog-Instanzen voraus. Es werden die Zugangsdaten dieser
  8760. Datenbankdefinition genutzt. <br>
  8761. Es werden nur Inhalte der Tabelle "history" berücksichtigt wenn nichts anderes beschrieben ist. <br><br>
  8762. Überblick welche anderen Perl-Module DbRep verwendet: <br><br>
  8763. Net::FTP (nur wenn FTP-Transfer nach Datenbank-Dump genutzt wird) <br>
  8764. Net::FTPSSL (nur wenn FTP-Transfer mit Verschlüsselung nach Datenbank-Dump genutzt wird) <br>
  8765. POSIX <br>
  8766. Time::HiRes <br>
  8767. Time::Local <br>
  8768. Scalar::Util <br>
  8769. DBI <br>
  8770. Color (FHEM-Modul) <br>
  8771. IO::Compress::Gzip <br>
  8772. IO::Uncompress::Gunzip <br>
  8773. Blocking (FHEM-Modul) <br><br>
  8774. Aus Performancegründen sollten zusätzlich folgender Index erstellt werden: <br>
  8775. <code>
  8776. CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;
  8777. </code>
  8778. </ul>
  8779. <br>
  8780. <a name="DbRepdefine"></a>
  8781. <b>Definition</b>
  8782. <br>
  8783. <ul>
  8784. <code>
  8785. define &lt;name&gt; DbRep &lt;Name der DbLog-Instanz&gt;
  8786. </code>
  8787. <br><br>
  8788. (&lt;Name der DbLog-Instanz&gt; - es wird der Name der auszuwertenden DBLog-Datenbankdefinition angegeben <b>nicht</b> der Datenbankname selbst)
  8789. </ul>
  8790. <br><br>
  8791. <a name="DbRepset"></a>
  8792. <b>Set </b>
  8793. <ul>
  8794. Zur Zeit gibt es folgende Set-Kommandos. Über sie werden die Auswertungen angestoßen und definieren selbst die Auswertungsvariante.
  8795. Nach welchen Kriterien die Datenbankinhalte durchsucht werden und die Aggregation erfolgt, wird durch <a href="#DbRepattr">Attribute</a> gesteuert.
  8796. <br><br>
  8797. <ul><ul>
  8798. <li><b> averageValue [display | writeToDB]</b>
  8799. - berechnet einen Durchschnittswert des Datenbankfelds "VALUE" in den
  8800. gegebenen Zeitgrenzen ( siehe <a href="#DbRepattr">Attribute</a>).
  8801. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  8802. angegeben sein. <br>
  8803. Mit dem Attribut "averageCalcForm" wird die Berechnungsvariante zur Mittelwertermittlung definiert.
  8804. Zur Zeit sind folgende Varianten implementiert: <br><br>
  8805. <ul>
  8806. <table>
  8807. <colgroup> <col width=5%> <col width=95%> </colgroup>
  8808. <tr><td> <b>avgArithmeticMean</b> </td><td>: es wird der arithmetische Mittelwert berechnet
  8809. (default) </td></tr>
  8810. <tr><td> <b>avgDailyMeanGWS</b> </td><td>: berechnet die Tagesmitteltemperatur entsprechend den
  8811. <a href='https://www.dwd.de/DE/leistungen/klimadatendeutschland/beschreibung_tagesmonatswerte.html'>Vorschriften</a> des Deutschen Wetterdienstes. <br>
  8812. Diese Variante verwendet automatisch die Aggregation "day". </td></tr>
  8813. <tr><td> <b>avgTimeWeightMean</b> </td><td>: berechnet den zeitgewichteten Mittelwert </td></tr>
  8814. </table>
  8815. </ul>
  8816. <br>
  8817. Ist keine oder die Option "display" angegeben, werden die Ergebnisse nur angezeigt. Mit
  8818. der Option "writeToDB" werden die Berechnungsergebnisse mit einem neuen Readingnamen
  8819. in der Datenbank gespeichert. <br>
  8820. Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet.
  8821. Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen. <br>
  8822. Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode
  8823. abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann.
  8824. Das Feld "EVENT" wird mit "calculated" gefüllt.<br><br>
  8825. <ul>
  8826. <b>Beispiel neuer Readingname gebildet aus dem Originalreading "totalpac":</b> <br>
  8827. avgam_day_totalpac <br>
  8828. # &lt;Bildungsfunktion&gt;_&lt;Aggregation&gt;_&lt;Originalreading&gt; <br>
  8829. </li> <br>
  8830. </ul>
  8831. <li><b> cancelDump </b> - bricht einen laufenden Datenbankdump ab. </li> <br>
  8832. <li><b> changeValue </b> - ändert den gespeicherten Wert eines Readings.
  8833. Ist die Selektion auf bestimmte Device/Reading-Kombinationen durch die
  8834. <a href="#DbRepattr">Attribute</a> "device" bzw. "reading" beschränkt, werden sie genauso
  8835. berücksichtigt wie gesetzte Zeitgrenzen (Attribute time.*). <br>
  8836. Fehlen diese Beschränkungen, wird die gesamte Datenbank durchsucht und der angegebene Wert
  8837. geändert. <br><br>
  8838. <ul>
  8839. <b>Syntax: </b> <br>
  8840. set &lt;name&gt; changeValue "&lt;alter String&gt;","&lt;neuer String&gt;" <br><br>
  8841. Die Strings werden in Doppelstrich eingeschlossen und durch Komma getrennt.
  8842. Dabei kann "String" sein: <br>
  8843. <pre>
  8844. &lt;alter String&gt; : * ein einfacher String mit/ohne Leerzeichen, z.B. "OL 12"
  8845. * ein String mit Verwendung von SQL-Wildcard, z.B. "%OL%"
  8846. &lt;neuer String&gt; : * ein einfacher String mit/ohne Leerzeichen, z.B. "12 kWh"
  8847. * Perl Code eingeschlossen in {}, z.B. {$VALUE = (split(",",$VALUE))[1]}.
  8848. Dem Perl-Ausdruck werden die Variablen $VALUE und $UNIT übergeben. Sie können innerhalb
  8849. des Perl-Code geändert werden. Der zurückgebene Wert von $VALUE und $UNIT wird in dem Feld
  8850. VALUE bzw. UNIT des Datensatzes gespeichert.
  8851. </pre>
  8852. <b>Beispiele: </b> <br>
  8853. set &lt;name&gt; changeValue "OL","12 OL" <br>
  8854. # der alte Feldwert "OL" wird in "12 OL" geändert. <br><br>
  8855. set &lt;name&gt; changeValue "%OL%","12 OL" <br>
  8856. # enthält das Feld VALUE den Teilstring "OL", wird es in "12 OL" geändert. <br><br>
  8857. set &lt;name&gt; changeValue "12 kWh","{$VALUE,$UNIT = split(" ",$VALUE)}" <br>
  8858. # der alte Feldwert "12 kWh" wird in VALUE=12 und UNIT=kWh gesplittet und in den Datenbankfeldern gespeichert <br><br>
  8859. set &lt;name&gt; changeValue "24%","{$VALUE = (split(" ",$VALUE))[0]}" <br>
  8860. # beginnt der alte Feldwert mit "24", wird er gesplittet und VALUE=24 gespeichert (z.B. "24 kWh")
  8861. <br><br>
  8862. Zusammengefasst sind die zur Steuerung von changeValue relevanten Attribute: <br><br>
  8863. <ul>
  8864. <table>
  8865. <colgroup> <col width=5%> <col width=95%> </colgroup>
  8866. <tr><td> <b>device</b> </td><td>: Selektion nur von Datensätzen die &lt;device&gt; enthalten </td></tr>
  8867. <tr><td> <b>reading</b> </td><td>: Selektion nur von Datensätzen die &lt;reading&gt; enthalten </td></tr>
  8868. <tr><td> <b>time.*</b> </td><td>: eine Reihe von Attributen zur Zeitabgrenzung </td></tr>
  8869. <tr><td> <b>executeBeforeProc</b> </td><td>: ausführen FHEM Kommando (oder perl-Routine) vor Start changeValue </td></tr>
  8870. <tr><td> <b>executeAfterProc</b> </td><td>: ausführen FHEM Kommando (oder perl-Routine) nach Ende changeValue </td></tr>
  8871. </table>
  8872. </ul>
  8873. <br>
  8874. <br>
  8875. <b>Hinweis:</b> <br>
  8876. Obwohl die Funktion selbst non-blocking ausgelegt ist, sollte das zugeordnete DbLog-Device
  8877. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden (Tabellen-Lock). <br><br>
  8878. </li> <br>
  8879. </ul>
  8880. <li><b> countEntries [history | current] </b>
  8881. - liefert die Anzahl der Tabelleneinträge (default: history) in den gegebenen
  8882. Zeitgrenzen (siehe <a href="#DbRepattr">Attribute</a>).
  8883. Sind die Timestamps nicht gesetzt werden alle Einträge gezählt.
  8884. Beschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw. Reading
  8885. gehen in die Selektion mit ein. </li> <br>
  8886. <li><b> delEntries </b> - löscht alle oder die durch die <a href="#DbRepattr">Attribute</a> device und/oder
  8887. reading definierten Datenbankeinträge. Die Eingrenzung über Timestamps erfolgt
  8888. folgendermaßen: <br><br>
  8889. <ul>
  8890. "timestamp_begin" gesetzt: gelöscht werden DB-Einträge <b>ab</b> diesem Zeitpunkt bis zum aktuellen Datum/Zeit <br>
  8891. "timestamp_end" gesetzt : gelöscht werden DB-Einträge <b>bis</b> bis zu diesem Zeitpunkt <br>
  8892. beide Timestamps gesetzt : gelöscht werden DB-Einträge <b>zwischen</b> diesen Zeitpunkten <br>
  8893. <br>
  8894. Aus Sicherheitsgründen muss das <a href="#DbRepattr">Attribut</a> "allowDeletion"
  8895. gesetzt sein um die Löschfunktion freizuschalten. <br>
  8896. </li>
  8897. <br>
  8898. </ul>
  8899. <li><b> delSeqDoublets [adviceRemain | adviceDelete | delete]</b> - zeigt bzw. löscht aufeinander folgende identische Datensätze.
  8900. Dazu wird Device,Reading und Value ausgewertet. Nicht gelöscht werden der erste und der letzte Datensatz
  8901. einer Aggregationsperiode (z.B. hour, day, week usw.) sowie die Datensätze vor oder nach einem Wertewechsel
  8902. (Datenbankfeld VALUE). <br>
  8903. Die <a href="#DbRepattr">Attribute</a> zur Aggregation,Zeit-,Device- und Reading-Abgrenzung werden dabei
  8904. berücksichtigt. Ist das Attribut "aggregation" nicht oder auf "no" gesetzt, wird als Standard die Aggregation
  8905. "day" verwendet. Für Datensätze mit numerischen Werten kann mit dem <a href="#DbRepattr">Attribut</a>
  8906. "seqDoubletsVariance" eine Abweichung eingestellt werden, bis zu der aufeinander folgende numerische Werte als
  8907. identisch angesehen und gelöscht werden sollen.
  8908. <br><br>
  8909. <ul>
  8910. <table>
  8911. <colgroup> <col width=5%> <col width=95%> </colgroup>
  8912. <tr><td> <b>adviceRemain</b> </td><td>: simuliert die nach der Operation in der DB verbleibenden Datensätze (es wird nichts gelöscht !) </td></tr>
  8913. <tr><td> <b>adviceDelete</b> </td><td>: simuliert die zu löschenden Datensätze (es wird nichts gelöscht !) </td></tr>
  8914. <tr><td> <b>delete</b> </td><td>: löscht die sequentiellen Dubletten (siehe Beispiel) </td></tr>
  8915. </table>
  8916. </ul>
  8917. <br>
  8918. Aus Sicherheitsgründen muss das <a href="#DbRepattr">Attribut</a> "allowDeletion" für die "delete" Option
  8919. gesetzt sein. <br>
  8920. Die Anzahl der anzuzeigenden Datensätze der Kommandos "delSeqDoublets adviceDelete", "delSeqDoublets adviceRemain" ist
  8921. zunächst begrenzt (default 1000) und kann durch das <a href="#DbRepattr">Attribut</a> "limit" angepasst werden.
  8922. Die Einstellung von "limit" hat keinen Einfluss auf die "delSeqDoublets delete" Funktion, sondern beeinflusst <b>NUR</b> die
  8923. Anzeige der Daten. <br>
  8924. Vor und nach der Ausführung von "delSeqDoublets" kann ein FHEM-Kommando bzw. Perl-Routine ausgeführt werden.
  8925. (siehe <a href="#DbRepattr">Attribute</a> "executeBeforeProc", "executeAfterProc")
  8926. <br><br>
  8927. <ul>
  8928. <b>Beispiel</b> - die nach Verwendung der delete-Option in der DB verbleibenden Datensätze sind <b>fett</b>
  8929. gekennzeichnet:<br><br>
  8930. <ul>
  8931. <b>2017-11-25_00-00-05__eg.az.fridge_Pwr__power 0 </b> <br>
  8932. 2017-11-25_00-02-26__eg.az.fridge_Pwr__power 0 <br>
  8933. 2017-11-25_00-04-33__eg.az.fridge_Pwr__power 0 <br>
  8934. 2017-11-25_01-06-10__eg.az.fridge_Pwr__power 0 <br>
  8935. <b>2017-11-25_01-08-21__eg.az.fridge_Pwr__power 0 </b> <br>
  8936. <b>2017-11-25_01-08-59__eg.az.fridge_Pwr__power 60.32 </b> <br>
  8937. <b>2017-11-25_01-11-21__eg.az.fridge_Pwr__power 56.26 </b> <br>
  8938. <b>2017-11-25_01-27-54__eg.az.fridge_Pwr__power 6.19 </b> <br>
  8939. <b>2017-11-25_01-28-51__eg.az.fridge_Pwr__power 0 </b> <br>
  8940. 2017-11-25_01-31-00__eg.az.fridge_Pwr__power 0 <br>
  8941. 2017-11-25_01-33-59__eg.az.fridge_Pwr__power 0 <br>
  8942. <b>2017-11-25_02-39-29__eg.az.fridge_Pwr__power 0 </b> <br>
  8943. <b>2017-11-25_02-41-18__eg.az.fridge_Pwr__power 105.28</b> <br>
  8944. <b>2017-11-25_02-41-26__eg.az.fridge_Pwr__power 61.52 </b> <br>
  8945. <b>2017-11-25_03-00-06__eg.az.fridge_Pwr__power 47.46 </b> <br>
  8946. <b>2017-11-25_03-00-33__eg.az.fridge_Pwr__power 0 </b> <br>
  8947. 2017-11-25_03-02-07__eg.az.fridge_Pwr__power 0 <br>
  8948. 2017-11-25_23-37-42__eg.az.fridge_Pwr__power 0 <br>
  8949. <b>2017-11-25_23-40-10__eg.az.fridge_Pwr__power 0 </b> <br>
  8950. <b>2017-11-25_23-42-24__eg.az.fridge_Pwr__power 1 </b> <br>
  8951. 2017-11-25_23-42-24__eg.az.fridge_Pwr__power 1 <br>
  8952. <b>2017-11-25_23-45-27__eg.az.fridge_Pwr__power 1 </b> <br>
  8953. <b>2017-11-25_23-47-07__eg.az.fridge_Pwr__power 0 </b> <br>
  8954. 2017-11-25_23-55-27__eg.az.fridge_Pwr__power 0 <br>
  8955. <b>2017-11-25_23-48-15__eg.az.fridge_Pwr__power 0 </b> <br>
  8956. <b>2017-11-25_23-50-21__eg.az.fridge_Pwr__power 59.1 </b> <br>
  8957. <b>2017-11-25_23-55-14__eg.az.fridge_Pwr__power 52.31 </b> <br>
  8958. <b>2017-11-25_23-58-09__eg.az.fridge_Pwr__power 51.73 </b> <br>
  8959. </ul>
  8960. </ul>
  8961. </li>
  8962. <br>
  8963. <br>
  8964. <li><b> deviceRename </b> - benennt den Namen eines Device innerhalb der angeschlossenen Datenbank (Internal
  8965. DATABASE) um.
  8966. Der Gerätename wird immer in der <b>gesamten</b> Datenbank umgesetzt. Eventuell gesetzte
  8967. Zeitgrenzen oder Beschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw.
  8968. Reading werden nicht berücksichtigt. <br><br>
  8969. <ul>
  8970. <b>Beispiel: </b><br>
  8971. set &lt;name&gt; deviceRename &lt;alter Devicename&gt;,&lt;neuer Devicename&gt; <br>
  8972. # Die Anzahl der umbenannten Device-Datensätze wird im Reading "device_renamed" ausgegeben. <br>
  8973. # Wird der umzubenennende Gerätename in der Datenbank nicht gefunden, wird eine WARNUNG im Reading "device_not_renamed" ausgegeben. <br>
  8974. # Entsprechende Einträge erfolgen auch im Logfile mit verbose=3
  8975. <br><br>
  8976. <b>Hinweis:</b> <br>
  8977. Obwohl die Funktion selbst non-blocking ausgelegt ist, sollte das zugeordnete DbLog-Device
  8978. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden (Tabellen-Lock). <br><br>
  8979. </li> <br>
  8980. </ul>
  8981. <li><b> diffValue [display | writeToDB] </b>
  8982. - berechnet den Differenzwert des Datenbankfelds "VALUE" in den Zeitgrenzen (Attribute) "timestamp_begin", "timestamp_end" bzw "timeDiffToNow / timeOlderThan".
  8983. Es muss das auszuwertende Reading im Attribut "reading" angegeben sein.
  8984. Diese Funktion ist z.B. zur Auswertung von Eventloggings sinnvoll, deren Werte sich fortlaufend erhöhen und keine Wertdifferenzen wegschreiben. <br>
  8985. 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
  8986. Zeitgrenzen/Aggregation gebildet, wobei ein Übertragswert der Vorperiode (Aggregation) zur darauf folgenden Aggregationsperiode
  8987. berücksichtigt wird sofern diese einen Value-Wert enhtält. <br>
  8988. Dabei wird ein Zählerüberlauf (Neubeginn bei 0) mit berücksichtigt (vergleiche <a href="#DbRepattr">Attribut</a> "diffAccept"). <br>
  8989. Wird in einer auszuwertenden Zeit- bzw. Aggregationsperiode nur ein Datensatz gefunden, kann die Differenz in Verbindung mit dem
  8990. Differenzübertrag der Vorperiode berechnet werden. in diesem Fall kann es zu einer logischen Ungenauigkeit in der Zuordnung der Differenz
  8991. zu der Aggregationsperiode kommen. Deswegen wird eine Warnung im "state" und das
  8992. Reading "less_data_in_period" mit einer Liste der betroffenen Perioden wird erzeugt. <br><br>
  8993. <ul>
  8994. <b>Hinweis: </b><br>
  8995. Im Auswertungs- bzw. Aggregationszeitraum (Tag, Woche, Monat, etc.) sollten dem Modul pro Periode mindestens ein Datensatz
  8996. zu Beginn und ein Datensatz gegen Ende des Aggregationszeitraumes zur Verfügung stehen um eine möglichst genaue Auswertung
  8997. der Differenzwerte vornehmen zu können.
  8998. <br>
  8999. <br>
  9000. </ul>
  9001. Ist keine oder die Option "display" angegeben, werden die Ergebnisse nur angezeigt. Mit
  9002. der Option "writeToDB" werden die Berechnungsergebnisse mit einem neuen Readingnamen
  9003. in der Datenbank gespeichert. <br>
  9004. Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet.
  9005. Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen. <br>
  9006. Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode
  9007. abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann.
  9008. Das Feld "EVENT" wird mit "calculated" gefüllt.<br><br>
  9009. <ul>
  9010. <b>Beispiel neuer Readingname gebildet aus dem Originalreading "totalpac":</b> <br>
  9011. diff_day_totalpac <br>
  9012. # &lt;Bildungsfunktion&gt;_&lt;Aggregation&gt;_&lt;Originalreading&gt; <br>
  9013. </li> <br>
  9014. </ul>
  9015. <li><b> dumpMySQL [clientSide | serverSide]</b>
  9016. - erstellt einen Dump der angeschlossenen MySQL-Datenbank. <br>
  9017. Abhängig von der ausgewählten Option wird der Dump auf der Client- bzw. Serverseite erstellt. <br>
  9018. Die Varianten unterscheiden sich hinsichtlich des ausführenden Systems, des Erstellungsortes, der
  9019. Attributverwendung, des erzielten Ergebnisses und der benötigten Hardwareressourcen. <br>
  9020. Die Option "clientSide" benötigt z.B. eine leistungsfähigere Hardware des FHEM-Servers, sichert aber alle
  9021. Tabellen inklusive eventuell angelegter Views. <br>
  9022. Mit dem Attribut "dumpCompress" kann eine Komprimierung der erstellten Dumpfiles eingeschaltet werden.
  9023. <br><br>
  9024. <ul>
  9025. <b><u>Option clientSide</u></b> <br>
  9026. Der Dump wird durch den Client (FHEM-Rechner) erstellt und per default im log-Verzeichnis des Clients
  9027. gespeichert.
  9028. Das Zielverzeichnis kann mit dem <a href="#DbRepattr">Attribut</a> "dumpDirLocal" verändert werden und muß auf
  9029. dem Client durch FHEM beschreibbar sein. <br>
  9030. Vor dem Dump kann eine Tabellenoptimierung (Attribut "optimizeTablesBeforeDump") oder ein FHEM-Kommando
  9031. (Attribut "executeBeforeProc") optional zugeschaltet werden.
  9032. Nach dem Dump kann ebenfalls ein FHEM-Kommando (siehe Attribut "executeAfterProc") ausgeführt werden. <br><br>
  9033. <b>Achtung ! <br>
  9034. Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die
  9035. Tabellenoptimierung verwendet wird ! </b> <br><br>
  9036. Über die <a href="#DbRepattr">Attribute</a> "dumpMemlimit" und "dumpSpeed" kann das Laufzeitverhalten der
  9037. Funktion beeinflusst werden um eine Optimierung bezüglich Performance und Ressourcenbedarf zu erreichen. <br><br>
  9038. Die für "dumpMySQL clientSide" relevanten Attribute sind: <br><br>
  9039. <ul>
  9040. <table>
  9041. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9042. <tr><td> dumpComment </td><td>: User-Kommentar im Dumpfile </td></tr>
  9043. <tr><td> dumpCompress </td><td>: Komprimierung des Dumpfiles nach der Erstellung </td></tr>
  9044. <tr><td> dumpDirLocal </td><td>: das lokale Zielverzeichnis für die Erstellung des Dump </td></tr>
  9045. <tr><td> dumpMemlimit </td><td>: Begrenzung der Speicherverwendung </td></tr>
  9046. <tr><td> dumpSpeed </td><td>: Begrenzung die CPU-Belastung </td></tr>
  9047. <tr><td> dumpFilesKeep </td><td>: Anzahl der aufzubwahrenden Dumpfiles </td></tr>
  9048. <tr><td> executeBeforeProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) vor dem Dump </td></tr>
  9049. <tr><td> executeAfterProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) nach dem Dump </td></tr>
  9050. <tr><td> optimizeTablesBeforeDump </td><td>: Tabelloptimierung vor dem Dump ausführen </td></tr>
  9051. </table>
  9052. </ul>
  9053. <br>
  9054. Nach einem erfolgreichen Dump werden alte Dumpfiles gelöscht und nur die Anzahl Files, definiert durch
  9055. das Attribut "dumpFilesKeep" (default: 3), verbleibt im Zielverzeichnis "dumpDirLocal". Falls "dumpFilesKeep = 0"
  9056. gesetzt ist, werden alle Dumpfiles (auch das aktuell erstellte File), gelöscht.
  9057. Diese Einstellung kann sinnvoll sein, wenn FTP aktiviert ist
  9058. und die erzeugten Dumps nur im FTP-Zielverzeichnis erhalten bleiben sollen. <br><br>
  9059. Die <b>Namenskonvention der Dumpfiles</b> ist: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sql[.gzip] <br><br>
  9060. Das erzeugte Dumpfile kann z.B. mit: <br><br>
  9061. <ul>
  9062. mysql -u &lt;user&gt; -p &lt;dbname&gt; < &lt;filename&gt;.sql <br><br>
  9063. </ul>
  9064. auf dem MySQL-Server ausgeführt werden um die Datenbank aus dem Dump wiederherzustellen. <br><br>
  9065. <br>
  9066. <b><u>Option serverSide</u></b> <br>
  9067. Der Dump wird durch den MySQL-Server erstellt und per default im Home-Verzeichnis des MySQL-Servers
  9068. gespeichert. <br>
  9069. Es wird die gesamte history-Tabelle (nicht current-Tabelle) <b>im CSV-Format</b> ohne
  9070. Einschränkungen exportiert. <br>
  9071. Vor dem Dump kann eine Tabellenoptimierung (Attribut "optimizeTablesBeforeDump")
  9072. optional zugeschaltet werden . <br><br>
  9073. <b>Achtung ! <br>
  9074. Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die
  9075. Tabellenoptimierung verwendet wird ! </b> <br><br>
  9076. Vor und nach dem Dump kann ein FHEM-Kommando (siehe Attribute "executeBeforeProc", "executeAfterProc") ausgeführt
  9077. werden. <br><br>
  9078. Die für "dumpMySQL serverSide" relevanten Attribute sind: <br><br>
  9079. <ul>
  9080. <table>
  9081. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9082. <tr><td> dumpDirRemote </td><td>: das Erstellungsverzeichnis des Dumpfile dem entfernten Server </td></tr>
  9083. <tr><td> dumpCompress </td><td>: Komprimierung des Dumpfiles nach der Erstellung </td></tr>
  9084. <tr><td> dumpDirLocal </td><td>: Directory des lokal gemounteten dumpDirRemote-Verzeichnisses </td></tr>
  9085. <tr><td> dumpFilesKeep </td><td>: Anzahl der aufzubwahrenden Dumpfiles </td></tr>
  9086. <tr><td> executeBeforeProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) vor dem Dump </td></tr>
  9087. <tr><td> executeAfterProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) nach dem Dump </td></tr>
  9088. <tr><td> optimizeTablesBeforeDump </td><td>: Tabelloptimierung vor dem Dump ausführen </td></tr>
  9089. </table>
  9090. </ul>
  9091. <br>
  9092. Das Zielverzeichnis kann mit dem <a href="#DbRepattr">Attribut</a> "dumpDirRemote" verändert werden.
  9093. Es muß sich auf dem MySQL-Host gefinden und durch den MySQL-Serverprozess beschreibbar sein. <br>
  9094. Der verwendete Datenbankuser benötigt das "FILE"-Privileg. <br><br>
  9095. <b>Hinweis:</b> <br>
  9096. Soll die interne Versionsverwaltung und die Dumpfilekompression des Moduls genutzt, sowie die Größe des erzeugten
  9097. Dumpfiles ausgegeben werden, ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten
  9098. und im <a href="#DbRepattr">Attribut</a> "dumpDirLocal" dem DbRep-Device bekannt zu machen. <br>
  9099. Gleiches gilt wenn der FTP-Transfer nach dem Dump genutzt werden soll (Attribut "ftpUse" bzw. "ftpUseSSL").
  9100. <br><br>
  9101. <ul>
  9102. <b>Beispiel: </b> <br>
  9103. attr &lt;name&gt; dumpDirRemote /volume1/ApplicationBackup/dumps_FHEM/ <br>
  9104. attr &lt;name&gt; dumpDirLocal /sds1/backup/dumps_FHEM/ <br>
  9105. attr &lt;name&gt; dumpFilesKeep 2 <br><br>
  9106. # Der Dump wird remote auf dem MySQL-Server im Verzeichnis '/volume1/ApplicationBackup/dumps_FHEM/'
  9107. erstellt. <br>
  9108. # Die interne Versionsverwaltung sucht im lokal gemounteten Verzeichnis '/sds1/backup/dumps_FHEM/'
  9109. vorhandene Dumpfiles und löscht diese bis auf die zwei letzten Versionen. <br>
  9110. <br>
  9111. </ul>
  9112. Wird die interne Versionsverwaltung genutzt, werden nach einem erfolgreichen Dump alte Dumpfiles gelöscht
  9113. und nur die Anzahl "dumpFilesKeep" (default: 3) verbleibt im Zielverzeichnis "dumpDirRemote".
  9114. FHEM benötigt in diesem Fall Schreibrechte auf dem Verzeichnis "dumpDirLocal". <br><br>
  9115. Die <b>Namenskonvention der Dumpfiles</b> ist: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.csv[.gzip] <br><br>
  9116. Ein Restore der Datenbank aus diesem Backup kann durch den Befehl: <br><br>
  9117. <ul>
  9118. set &lt;name&gt; &lt;restoreMySQL&gt; &lt;filename&gt;.csv[.gzip] <br><br>
  9119. </ul>
  9120. gestartet werden. <br><br>
  9121. <b><u>FTP Transfer nach Dump</u></b> <br>
  9122. Wenn diese Möglichkeit genutzt werden soll, ist das <a href="#DbRepattr">Attribut</a> "ftpUse" oder
  9123. "ftpUseSSL" zu setzen. Letzteres gilt wenn eine verschlüsselte Übertragung genutzt werden soll. <br>
  9124. Das Modul übernimmt ebenfalls die Versionierung der Dumpfiles im FTP-Zielverzeichnis mit Hilfe des Attributes
  9125. "ftpDumpFilesKeep".
  9126. Für die FTP-Übertragung relevante <a href="#DbRepattr">Attribute</a> sind: <br><br>
  9127. <ul>
  9128. <table>
  9129. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9130. <tr><td> ftpUse </td><td>: FTP Transfer nach dem Dump wird eingeschaltet (ohne SSL Verschlüsselung) </td></tr>
  9131. <tr><td> ftpUser </td><td>: User zur Anmeldung am FTP-Server, default: anonymous </td></tr>
  9132. <tr><td> ftpUseSSL </td><td>: FTP Transfer mit SSL Verschlüsselung nach dem Dump wird eingeschaltet </td></tr>
  9133. <tr><td> ftpDebug </td><td>: Debugging des FTP Verkehrs zur Fehlersuche </td></tr>
  9134. <tr><td> ftpDir </td><td>: Verzeichnis auf dem FTP-Server in welches das File übertragen werden soll (default: "/") </td></tr>
  9135. <tr><td> ftpDumpFilesKeep </td><td>: Es wird die angegebene Anzahl Dumpfiles im &lt;ftpDir&gt; belassen (default: 3) </td></tr>
  9136. <tr><td> ftpPassive </td><td>: setzen wenn passives FTP verwendet werden soll </td></tr>
  9137. <tr><td> ftpPort </td><td>: FTP-Port, default: 21 </td></tr>
  9138. <tr><td> ftpPwd </td><td>: Passwort des FTP-Users, default nicht gesetzt </td></tr>
  9139. <tr><td> ftpServer </td><td>: Name oder IP-Adresse des FTP-Servers. <b>notwendig !</b> </td></tr>
  9140. <tr><td> ftpTimeout </td><td>: Timeout für die FTP-Verbindung in Sekunden (default: 30). </td></tr>
  9141. </table>
  9142. </ul>
  9143. <br>
  9144. <br>
  9145. </ul>
  9146. </li><br>
  9147. <li><b> dumpSQLite </b> - erstellt einen Dump der angeschlossenen SQLite-Datenbank. <br>
  9148. Diese Funktion nutzt die SQLite Online Backup API und ermöglicht es konsistente Backups der SQLite-DB
  9149. in laufenden Betrieb zu erstellen.
  9150. Der Dump wird per default im log-Verzeichnis des FHEM-Rechners gespeichert.
  9151. Das Zielverzeichnis kann mit dem <a href="#DbRepattr">Attribut</a> "dumpDirLocal" verändert werden und muß
  9152. durch FHEM beschreibbar sein.
  9153. Vor dem Dump kann optional eine Tabellenoptimierung (Attribut "optimizeTablesBeforeDump")
  9154. zugeschaltet werden.
  9155. <br><br>
  9156. <b>Achtung ! <br>
  9157. Um ein Blockieren von FHEM zu vermeiden, muß DbLog im asynchronen Modus betrieben werden wenn die
  9158. Tabellenoptimierung verwendet wird ! </b> <br><br>
  9159. Vor und nach dem Dump kann ein FHEM-Kommando (siehe Attribute "executeBeforeProc", "executeAfterProc")
  9160. ausgeführt werden. <br><br>
  9161. Die für diese Funktion relevanten Attribute sind: <br><br>
  9162. <ul>
  9163. <table>
  9164. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9165. <tr><td> dumpCompress </td><td>: Komprimierung des Dumpfiles nach der Erstellung </td></tr>
  9166. <tr><td> dumpDirLocal </td><td>: Directory des lokal gemounteten dumpDirRemote-Verzeichnisses </td></tr>
  9167. <tr><td> dumpFilesKeep </td><td>: Anzahl der aufzubwahrenden Dumpfiles </td></tr>
  9168. <tr><td> executeBeforeProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) vor dem Dump </td></tr>
  9169. <tr><td> executeAfterProc </td><td>: ausführen FHEM Kommando (oder perl-Routine) nach dem Dump </td></tr>
  9170. <tr><td> optimizeTablesBeforeDump </td><td>: Tabelloptimierung vor dem Dump ausführen </td></tr>
  9171. </table>
  9172. </ul>
  9173. <br>
  9174. Nach einem erfolgreichen Dump werden alte Dumpfiles gelöscht und nur die Anzahl Files, definiert durch das
  9175. Attribut "dumpFilesKeep" (default: 3), verbleibt im Zielverzeichnis "dumpDirLocal". Falls "dumpFilesKeep = 0" gesetzt, werden
  9176. alle Dumpfiles (auch das aktuell erstellte File), gelöscht. Diese Einstellung kann sinnvoll sein, wenn FTP aktiviert ist
  9177. und die erzeugten Dumps nur im FTP-Zielverzeichnis erhalten bleiben sollen. <br><br>
  9178. Die <b>Namenskonvention der Dumpfiles</b> ist: &lt;dbname&gt;_&lt;date&gt;_&lt;time&gt;.sqlitebkp[.gzip] <br><br>
  9179. Die Datenbank kann mit "set &lt;name&gt; restoreSQLite &lt;Filename&gt;" wiederhergestellt
  9180. werden. <br>
  9181. Das erstellte Dumpfile kann auf einen FTP-Server übertragen werden. Siehe dazu die Erläuterungen
  9182. unter "dumpMySQL". <br><br>
  9183. </li><br>
  9184. <li><b> eraseReadings </b> - Löscht alle angelegten Readings im Device, außer dem Reading "state" und Readings, die in der
  9185. Ausnahmeliste definiert mit Attribut "readingPreventFromDel" enthalten sind.
  9186. </li><br>
  9187. <li><b> exportToFile </b> - exportiert DB-Einträge im CSV-Format in den gegebenen Zeitgrenzen.
  9188. Einschränkungen durch die <a href="#DbRepattr">Attribute</a> Device bzw. Reading gehen in die Selektion mit ein.
  9189. Der Filename wird durch das <a href="#DbRepattr">Attribut</a> "expimpfile" bestimmt. <br>
  9190. Durch das Attribut "aggregation" wird der Export der Datensätze in Zeitscheiben der angegebenen Aggregation
  9191. vorgenommen. Ist z.B. "aggregation = month" gesetzt, werden die Daten in monatlichen Paketen selektiert und in
  9192. das Exportfile geschrieben. Dadurch wird die Hauptspeicherverwendung optimiert wenn sehr große Datenmengen
  9193. exportiert werden sollen und vermeidet den "died prematurely" Abbruchfehler.
  9194. </li><br>
  9195. <li><b> fetchrows [history|current] </b>
  9196. - liefert <b>alle</b> Tabelleneinträge (default: history)
  9197. in den gegebenen Zeitgrenzen bzw. Selektionsbedingungen durch die <a href="#DbRepattr">Attribute</a>
  9198. "device" und "reading".
  9199. Eine evtl. gesetzte Aggregation wird dabei <b>nicht</b> berücksichtigt. <br>
  9200. Die Leserichtung in der Datenbank kann durch das <a href="#DbRepattr">Attribut</a>
  9201. "fetchRoute" bestimmt werden. <br><br>
  9202. Jedes Ergebnisreading setzt sich aus dem Timestring des Datensatzes, einem Index, dem Device
  9203. und dem Reading zusammen.
  9204. Die Funktion fetchrows ist in der Lage mehrfach vorkommende Datensätze (Dubletten) zu erkennen.
  9205. Solche Dubletten sind mit einem Index > 1 gekennzeichnet. <br>
  9206. Dubletten können mit dem Attribut "fetchMarkDuplicates" farblich hervorgehoben werden. <br><br>
  9207. <b>Hinweis:</b> <br>
  9208. Hervorgehobene Readings werden nach einem Restart bzw. nach rereadcfg nicht mehr angezeigt da
  9209. sie nicht im statefile gesichert werden (Verletzung erlaubter Readingnamen durch Formatierung).
  9210. <br><br>
  9211. Dieses Attribut ist mit einigen Farben vorbelegt, kann aber mit dem colorpicker-Widget
  9212. überschrieben werden: <br><br>
  9213. <ul>
  9214. <code>
  9215. attr &lt;name&gt; widgetOverride fetchMarkDuplicates:colorpicker
  9216. </code>
  9217. </ul>
  9218. <br>
  9219. Die Ergebnisreadings von fetchrows sind nach folgendem Schema aufgebaut: <br><br>
  9220. <ul>
  9221. <b>Beispiel:</b> <br>
  9222. 2017-10-22_03-04-43__1__SMA_Energymeter__Bezug_WirkP_Kosten_Diff <br>
  9223. # &lt;Datum&gt;_&lt;Zeit&gt;__&lt;Index&gt;__&lt;Device&gt;__&lt;Reading&gt;
  9224. </ul>
  9225. <br>
  9226. Zur besseren Übersicht sind die zur Steuerung von fetchrows relevanten Attribute hier noch einmal
  9227. dargestellt: <br><br>
  9228. <ul>
  9229. <table>
  9230. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9231. <tr><td> <b>fetchRoute</b> </td><td>: Leserichtung des Selekts innerhalb der Datenbank </td></tr>
  9232. <tr><td> <b>limit</b> </td><td>: begrenzt die Anzahl zu selektierenden bzw. anzuzeigenden Datensätze </td></tr>
  9233. <tr><td> <b>fetchMarkDuplicates</b> </td><td>: Hervorhebung von gefundenen Dubletten </td></tr>
  9234. <tr><td> <b>device</b> </td><td>: Selektion nur von Datensätzen die &lt;device&gt; enthalten </td></tr>
  9235. <tr><td> <b>reading</b> </td><td>: Selektion nur von Datensätzen die &lt;reading&gt; enthalten </td></tr>
  9236. <tr><td> <b>time.*</b> </td><td>: eine Reihe von Attributen zur Zeitabgrenzung </td></tr>
  9237. </table>
  9238. </ul>
  9239. <br>
  9240. <br>
  9241. <b>Hinweis:</b> <br>
  9242. Auch wenn das Modul bezüglich der Datenbankabfrage nichtblockierend arbeitet, kann eine
  9243. zu große Ergebnismenge (Anzahl Zeilen bzw. Readings) die Browsersesssion bzw. FHEMWEB
  9244. blockieren. Aus diesem Grund wird die Ergebnismenge mit dem
  9245. <a href="#DbRepattrlimit">Attribut</a> "limit" begrenzt. Bei Bedarf kann dieses Attribut
  9246. geändert werden, falls eine Anpassung der Selektionsbedingungen nicht möglich oder
  9247. gewünscht ist. <br><br>
  9248. </li> <br>
  9249. <li><b> insert </b> - Manuelles Einfügen eines Datensatzes in die Tabelle "history". Obligatorisch sind Eingabewerte für Datum, Zeit und Value.
  9250. 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>
  9251. <ul>
  9252. <b>Eingabeformat: </b> Datum,Zeit,Value,[Unit] <br>
  9253. # Unit ist optional, Attribute "reading" und "device" müssen gesetzt sein <br>
  9254. # Soll "Value=0" eingefügt werden, ist "Value = 0.0" zu verwenden. <br><br>
  9255. <b>Beispiel: </b> 2016-08-01,23:00:09,TestValue,TestUnit <br>
  9256. # Es sind KEINE Leerzeichen im Feldwert erlaubt !<br>
  9257. <br>
  9258. <b>Hinweis: </b><br>
  9259. Bei der Eingabe ist darauf zu achten dass im beabsichtigten Aggregationszeitraum (Tag, Woche, Monat, etc.) MINDESTENS zwei
  9260. Datensätze für die Funktion diffValue zur Verfügung stehen. Ansonsten kann keine Differenz berechnet werden und diffValue
  9261. gibt in diesem Fall "0" in der betroffenen Periode aus !
  9262. <br>
  9263. <br>
  9264. </li>
  9265. </ul>
  9266. <li><b> importFromFile </b> - importiert Datensätze im CSV-Format aus einem File in die Datenbank. Der Filename wird
  9267. durch das <a href="#DbRepattr">Attribut</a> "expimpfile" bestimmt. <br><br>
  9268. <ul>
  9269. <b>Datensatzformat: </b> "TIMESTAMP","DEVICE","TYPE","EVENT","READING","VALUE","UNIT" <br><br>
  9270. # Die Felder "TIMESTAMP","DEVICE","TYPE","EVENT","READING" und "VALUE" müssen gesetzt sein. Das Feld "UNIT" ist optional.
  9271. Der Fileinhalt wird als Transaktion importiert, d.h. es wird der Inhalt des gesamten Files oder, im Fehlerfall, kein Datensatz des Files importiert.
  9272. 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
  9273. das Logfile geschrieben werden was FHEM blockieren oder überlasten könnte. <br><br>
  9274. <b>Beispiel: </b> "2016-09-25 08:53:56","STP_5000","SMAUTILS","etotal: 11859.573","etotal","11859.573","" <br>
  9275. <br>
  9276. </li> <br>
  9277. </ul>
  9278. <li><b> maxValue [display | writeToDB] </b>
  9279. - berechnet den Maximalwert des Datenbankfelds "VALUE" in den Zeitgrenzen
  9280. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  9281. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  9282. angegeben sein.
  9283. Die Auswertung enthält den Zeitstempel des ermittelten Maximumwertes innerhalb der
  9284. Aggregation bzw. Zeitgrenzen.
  9285. Im Reading wird der Zeitstempel des <b>letzten</b> Auftretens vom Maximalwert ausgegeben
  9286. falls dieser Wert im Intervall mehrfach erreicht wird. <br>
  9287. Ist keine oder die Option "display" angegeben, werden die Ergebnisse nur angezeigt. Mit
  9288. der Option "writeToDB" werden die Berechnungsergebnisse mit einem neuen Readingnamen
  9289. in der Datenbank gespeichert. <br>
  9290. Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet.
  9291. Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen. <br>
  9292. Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode
  9293. abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann.
  9294. Das Feld "EVENT" wird mit "calculated" gefüllt.<br><br>
  9295. <ul>
  9296. <b>Beispiel neuer Readingname gebildet aus dem Originalreading "totalpac":</b> <br>
  9297. max_day_totalpac <br>
  9298. # &lt;Bildungsfunktion&gt;_&lt;Aggregation&gt;_&lt;Originalreading&gt; <br>
  9299. </li> <br>
  9300. </ul>
  9301. <li><b> minValue [display | writeToDB]</b>
  9302. - berechnet den Minimalwert des Datenbankfelds "VALUE" in den Zeitgrenzen
  9303. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  9304. Es muss das auszuwertende Reading über das <a href="#DbRepattr">Attribut</a> "reading"
  9305. angegeben sein.
  9306. Die Auswertung enthält den Zeitstempel des ermittelten Minimumwertes innerhalb der
  9307. Aggregation bzw. Zeitgrenzen.
  9308. Im Reading wird der Zeitstempel des <b>ersten</b> Auftretens vom Minimalwert ausgegeben
  9309. falls dieser Wert im Intervall mehrfach erreicht wird. <br>
  9310. Ist keine oder die Option "display" angegeben, werden die Ergebnisse nur angezeigt. Mit
  9311. der Option "writeToDB" werden die Berechnungsergebnisse mit einem neuen Readingnamen
  9312. in der Datenbank gespeichert. <br>
  9313. Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet.
  9314. Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen. <br>
  9315. Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode
  9316. abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann.
  9317. Das Feld "EVENT" wird mit "calculated" gefüllt.<br><br>
  9318. <ul>
  9319. <b>Beispiel neuer Readingname gebildet aus dem Originalreading "totalpac":</b> <br>
  9320. min_day_totalpac <br>
  9321. # &lt;Bildungsfunktion&gt;_&lt;Aggregation&gt;_&lt;Originalreading&gt; <br>
  9322. </li> <br>
  9323. </ul>
  9324. <li><b> optimizeTables </b> - optimiert die Tabellen in der angeschlossenen Datenbank (MySQL). <br>
  9325. Vor und nach der Optimierung kann ein FHEM-Kommando ausgeführt werden.
  9326. (siehe <a href="#DbRepattr">Attribute</a> "executeBeforeProc", "executeAfterProc")
  9327. <br><br>
  9328. <ul>
  9329. <b>Hinweis:</b> <br>
  9330. Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device
  9331. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden. <br><br>
  9332. </li>
  9333. </ul><br>
  9334. <li><b> readingRename </b> - benennt den Namen eines Readings innerhalb der angeschlossenen Datenbank (siehe Internal DATABASE) um.
  9335. Der Readingname wird immer in der <b>gesamten</b> Datenbank umgesetzt. Eventuell
  9336. gesetzte Zeitgrenzen oder Beschränkungen durch die <a href="#DbRepattr">Attribute</a>
  9337. Device bzw. Reading werden nicht berücksichtigt. <br><br>
  9338. <ul>
  9339. <b>Beispiel: </b><br>
  9340. set &lt;name&gt; readingRename &lt;alter Readingname&gt;,&lt;neuer Readingname&gt; <br>
  9341. # Die Anzahl der umbenannten Device-Datensätze wird im Reading "reading_renamed"
  9342. ausgegeben. <br>
  9343. # Wird der umzubenennende Readingname in der Datenbank nicht gefunden, wird eine
  9344. WARNUNG im Reading "reading_not_renamed" ausgegeben. <br>
  9345. # Entsprechende Einträge erfolgen auch im Logfile mit verbose=3.
  9346. <br><br>
  9347. <b>Hinweis:</b> <br>
  9348. Obwohl die Funktion selbst non-blocking ausgelegt ist, sollte das zugeordnete DbLog-Device
  9349. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden (Tabellen-Lock). <br><br>
  9350. </li> <br>
  9351. </ul>
  9352. <li><b> repairSQLite </b> - repariert eine korrupte SQLite-Datenbank. <br>
  9353. Eine Korruption liegt im Allgemeinen vor wenn die Fehlermitteilung "database disk image is malformed"
  9354. im state des DbLog-Devices erscheint.
  9355. Wird dieses Kommando gestartet, wird das angeschlossene DbLog-Device zunächst automatisch für 10 Stunden
  9356. (36000 Sekunden) von der Datenbank getrennt (Trennungszeit). Nach Abschluss der Reparatur erfolgt
  9357. wieder eine sofortige Neuverbindung zur reparierten Datenbank. <br>
  9358. Dem Befehl kann eine abweichende Trennungszeit (in Sekunden) als Argument angegeben werden. <br>
  9359. Die korrupte Datenbank wird als &lt;database&gt;.corrupt im gleichen Verzeichnis gespeichert. <br><br>
  9360. <ul>
  9361. <b>Beispiel: </b><br>
  9362. set &lt;name&gt; repairSQLite <br>
  9363. # Die Datenbank wird repariert, Trennungszeit beträgt 10 Stunden <br>
  9364. set &lt;name&gt; repairSQLite 600 <br>
  9365. # Die Datenbank wird repariert, Trennungszeit beträgt 10 Minuten
  9366. <br><br>
  9367. <b>Hinweis:</b> <br>
  9368. Es ist nicht garantiert, dass die Reparatur erfolgreich verläuft und keine Daten verloren gehen.
  9369. Je nach Schwere der Korruption kann Datenverlust auftreten oder die Reparatur scheitern, auch wenn
  9370. kein Fehler im Ablauf signalisiert wird. Ein Backup der Datenbank sollte unbedingt vorhanden
  9371. sein ! <br><br>
  9372. </li> <br>
  9373. </ul>
  9374. <li><b> restoreMySQL &lt;File&gt;.csv[.gzip] </b> - importiert den Inhalt der history-Tabelle aus einem serverSide-Backup. <br>
  9375. Die Funktion stellt über eine Drop-Down Liste eine Dateiauswahl für den Restore zur Verfügung.
  9376. Dazu ist das Verzeichnis "dumpDirRemote" des MySQL-Servers auf dem Client zu mounten
  9377. und im <a href="#DbRepattr">Attribut</a> "dumpDirLocal" dem DbRep-Device bekannt zu machen. <br>
  9378. Es werden alle Files mit der Endung "csv[.gzip]" und deren Name mit der
  9379. verbundenen Datenbank beginnt (siehe Internal DATABASE), aufgelistet . <br><br>
  9380. </li><br>
  9381. <li><b> restoreSQLite &lt;File&gt;.sqlitebkp[.gzip] </b> - stellt das Backup einer SQLite-Datenbank wieder her. <br>
  9382. Die Funktion stellt über eine Drop-Down Liste die für den Restore zur Verfügung stehenden Dateien
  9383. zur Verfügung. Die aktuell in der Zieldatenbank enthaltenen Daten werden gelöscht bzw.
  9384. überschrieben.
  9385. Es werden alle Files mit der Endung "sqlitebkp[.gzip]" und deren Name mit dem Namen der
  9386. verbundenen Datenbank beginnt, aufgelistet . <br><br>
  9387. </li><br>
  9388. <li><b> sqlCmd </b> - führt ein beliebiges Benutzer spezifisches Kommando aus. <br>
  9389. Enthält dieses Kommando eine Delete-Operation, muss zur Sicherheit das
  9390. <a href="#DbRepattr">Attribut</a> "allowDeletion" gesetzt sein. <br>
  9391. Bei der Ausführung dieses Kommandos werden keine Einschränkungen durch gesetzte Attribute
  9392. "device", "reading", "time.*" bzw. "aggregation" berücksichtigt. <br>
  9393. Sollen die im Modul gesetzten <a href="#DbRepattr">Attribute</a> "timestamp_begin" bzw.
  9394. "timestamp_end" im Statement berücksichtigt werden, können die Platzhalter
  9395. "<b>§timestamp_begin§</b>" bzw. "<b>§timestamp_end§</b>" dafür verwendet werden. <br><br>
  9396. Soll ein Datensatz upgedated werden, ist dem Statement "TIMESTAMP=TIMESTAMP" hinzuzufügen um eine Änderung des
  9397. originalen Timestamps zu verhindern. <br><br>
  9398. <ul>
  9399. <b>Beispiele für Statements: </b> <br><br>
  9400. <ul>
  9401. <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>
  9402. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= "2017-05-06 00:00:00" group by DEVICE </li>
  9403. <li>set &lt;name&gt; sqlCmd select DEVICE, count(*) from history where TIMESTAMP >= §timestamp_begin§ group by DEVICE </li>
  9404. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE like "Te%t" order by `TIMESTAMP` desc </li>
  9405. <li>set &lt;name&gt; sqlCmd select * from history where `TIMESTAMP` > "2017-05-09 18:03:00" order by `TIMESTAMP` desc </li>
  9406. <li>set &lt;name&gt; sqlCmd select * from current order by `TIMESTAMP` desc </li>
  9407. <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>
  9408. <li>set &lt;name&gt; sqlCmd delete from current </li>
  9409. <li>set &lt;name&gt; sqlCmd delete from history where TIMESTAMP < "2016-05-06 00:00:00" </li>
  9410. <li>set &lt;name&gt; sqlCmd update history set TIMESTAMP=TIMESTAMP,VALUE='Val' WHERE VALUE='TestValue' </li>
  9411. <li>set &lt;name&gt; sqlCmd select * from history where DEVICE = "Test" </li>
  9412. <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>
  9413. </ul>
  9414. <br>
  9415. Das Ergebnis des Statements wird im <a href="#DbRepReadings">Reading</a> "SqlResult" dargestellt.
  9416. Die Ergebnis-Formatierung kann durch das <a href="#DbRepattr">Attribut</a> "sqlResultFormat" ausgewählt, sowie der verwendete
  9417. Feldtrenner durch das <a href="#DbRepattr">Attribut</a> "sqlResultFieldSep" festgelegt werden. <br><br>
  9418. Das Modul stellt optional eine Kommando-Historie zur Verfügung sobald ein SQL-Kommando erfolgreich
  9419. ausgeführt wurde.
  9420. Um diese Option zu nutzen, ist das Attribut "sqlCmdHistoryLength" mit der gewünschten Listenlänge
  9421. zu aktivieren. <br><br>
  9422. Zur besseren Übersicht sind die zur Steuerung von sqlCmd relevanten Attribute hier noch einmal
  9423. dargestellt: <br><br>
  9424. <ul>
  9425. <table>
  9426. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9427. <tr><td> <b>allowDeletion</b> </td><td>: aktiviert Löschmöglichkeit </td></tr>
  9428. <tr><td> <b>sqlResultFormat</b> </td><td>: legt die Darstellung des Kommandoergebnis fest </td></tr>
  9429. <tr><td> <b>sqlResultFieldSep</b> </td><td>: Auswahl Feldtrenner im Ergebnis </td></tr>
  9430. <tr><td> <b>sqlCmdHistoryLength</b> </td><td>: Aktivierung Kommando-Historie und deren Umfang</td></tr>
  9431. </table>
  9432. </ul>
  9433. <br>
  9434. <br>
  9435. <b>Hinweis:</b> <br>
  9436. Auch wenn das Modul bezüglich der Datenbankabfrage nichtblockierend arbeitet, kann eine
  9437. zu große Ergebnismenge (Anzahl Zeilen bzw. Readings) die Browsersesssion bzw. FHEMWEB
  9438. blockieren. Wenn man sich unsicher ist, sollte man vorsorglich dem Statement ein Limit
  9439. hinzufügen. <br><br>
  9440. </li> <br>
  9441. </ul>
  9442. <li><b> sqlCmdHistory </b> - Wenn mit dem <a href="#DbRepattr">Attribut</a> "sqlCmdHistoryLength" aktiviert, kann
  9443. aus einer Liste ein bereits erfolgreich ausgeführtes sqlCmd-Kommando wiederholt werden. <br>
  9444. Mit Ausführung des letzten Eintrags der Liste, "__purge_historylist__", kann die Liste gelöscht
  9445. werden. <br>
  9446. Falls das Statement "," enthält, wird dieses Zeichen aus technischen Gründen in der
  9447. History-Liste als "&lt;c&gt;" dargestellt. <br>
  9448. </li><br>
  9449. <li><b> sumValue [display | writeToDB]</b>
  9450. - Berechnet die Summenwerte des Datenbankfelds "VALUE" in den Zeitgrenzen
  9451. (Attribute) "timestamp_begin", "timestamp_end" bzw. "timeDiffToNow / timeOlderThan".
  9452. Es muss das auszuwertende Reading im <a href="#DbRepattr">Attribut</a> "reading"
  9453. angegeben sein. Diese Funktion ist sinnvoll wenn fortlaufend Wertedifferenzen eines
  9454. Readings in die Datenbank geschrieben werden. <br>
  9455. Ist keine oder die Option "display" angegeben, werden die Ergebnisse nur angezeigt. Mit
  9456. der Option "writeToDB" werden die Berechnungsergebnisse mit einem neuen Readingnamen
  9457. in der Datenbank gespeichert. <br>
  9458. Der neue Readingname wird aus einem Präfix und dem originalen Readingnamen gebildet.
  9459. Der Präfix setzt sich aus der Bildungsfunktion und der Aggregation zusammen. <br>
  9460. Der Timestamp der neuen Readings in der Datenbank wird von der eingestellten Aggregationsperiode
  9461. abgeleitet, sofern kein eindeutiger Zeitpunkt des Ergebnisses bestimmt werden kann.
  9462. Das Feld "EVENT" wird mit "calculated" gefüllt.<br><br>
  9463. <ul>
  9464. <b>Beispiel neuer Readingname gebildet aus dem Originalreading "totalpac":</b> <br>
  9465. sum_day_totalpac <br>
  9466. # &lt;Bildungsfunktion&gt;_&lt;Aggregation&gt;_&lt;Originalreading&gt; <br>
  9467. </li> <br>
  9468. </ul>
  9469. <br>
  9470. <li><b> syncStandby &lt;DbLog-Device Standby&gt; </b>
  9471. - Es werden die Datensätze aus der angeschlossenen Datenbank (Quelle) direkt in eine weitere
  9472. Datenbank (Standby-Datenbank) übertragen.
  9473. Dabei ist "&lt;DbLog-Device Standby&gt;" das DbLog-Device, welches mit der Standby-Datenbank
  9474. verbunden ist. <br><br>
  9475. Es werden alle Datensätze übertragen, die durch Timestamp-<a href="#DbRepattrlimit">Attribute</a>
  9476. bzw. die Attribute "device", "reading" bestimmt sind. <br>
  9477. Die Datensätze werden dabei in Zeitscheiben entsprechend der eingestellten Aggregation übertragen.
  9478. Hat das Attribut "aggregation" den Wert "no" oder "month", werden die Datensätze automatisch
  9479. in Tageszeitscheiben zur Standby-Datenbank übertragen.
  9480. Quell- und Standby-Datenbank können unterschiedlichen Typs sein.
  9481. <br><br>
  9482. Die zur Steuerung der syncStandby Funktion relevanten Attribute sind: <br><br>
  9483. <ul>
  9484. <table>
  9485. <colgroup> <col width=5%> <col width=95%> </colgroup>
  9486. <tr><td> <b>aggregation</b> </td><td>: Einstellung der Zeitscheiben zur Übertragung (hour,day,week) </td></tr>
  9487. <tr><td> <b>device</b> </td><td>: Übertragung nur von Datensätzen die &lt;device&gt; enthalten </td></tr>
  9488. <tr><td> <b>reading</b> </td><td>: Übertragung nur von Datensätzen die &lt;reading&gt; enthalten </td></tr>
  9489. <tr><td> <b>time.*</b> </td><td>: Attribute zur Zeitabgrenzung der zu übertragenden Datensätze. </td></tr>
  9490. </table>
  9491. </ul>
  9492. <br>
  9493. <br>
  9494. </li> <br>
  9495. <li><b> tableCurrentFillup </b> - Die current-Tabelle wird mit einem Extrakt der history-Tabelle aufgefüllt.
  9496. Die <a href="#DbRepattr">Attribute</a> zur Zeiteinschränkung bzw. device, reading werden ausgewertet.
  9497. Dadurch kann der Inhalt des Extrakts beeinflusst werden. Im zugehörigen DbLog-Device sollte sollte das Attribut
  9498. "DbLogType=SampleFill/History" gesetzt sein. </li> <br>
  9499. <li><b> tableCurrentPurge </b> - löscht den Inhalt der current-Tabelle. Es werden keine Limitierungen, z.B. durch die Attribute "timestamp_begin",
  9500. "timestamp_end", device, reading, usw. , ausgewertet. </li> <br>
  9501. <li><b> vacuum </b> - optimiert die Tabellen in der angeschlossenen Datenbank (SQLite, PostgreSQL). <br>
  9502. Vor und nach der Optimierung kann ein FHEM-Kommando ausgeführt werden.
  9503. (siehe <a href="#DbRepattr">Attribute</a> "executeBeforeProc", "executeAfterProc")
  9504. <br><br>
  9505. <ul>
  9506. <b>Hinweis:</b> <br>
  9507. Obwohl die Funktion selbst non-blocking ausgelegt ist, muß das zugeordnete DbLog-Device
  9508. im asynchronen Modus betrieben werden um ein Blockieren von FHEM zu vermeiden. <br><br>
  9509. </li>
  9510. </ul><br>
  9511. <br>
  9512. </ul></ul>
  9513. <b>Für alle Auswertungsvarianten (Ausnahme sqlCmd,deviceRename,readingRename) gilt: </b> <br>
  9514. Zusätzlich zu dem auszuwertenden Reading kann das Device mit angegeben werden um das Reporting nach diesen Kriterien einzuschränken.
  9515. Sind keine Zeitgrenzen-Attribute angegeben jedoch das Aggregations-Attribut gesetzt, wird der Zeitstempel des ältesten
  9516. Datensatzes in der Datenbank als Startdatum und das aktuelle Datum/die aktuelle Zeit als Zeitgrenze genutzt.
  9517. Konnte der älteste Datensatz in der Datenbank nicht ermittelt werden, wird '1970-01-01 01:00:00' als Selektionsstart
  9518. genutzt (siehe get &lt;name&gt; minTimestamp).
  9519. Sind weder Zeitgrenzen-Attribute noch Aggregation angegeben, wird die Datenselektion ohne Timestamp-Einschränkungen
  9520. ausgeführt.
  9521. <br><br>
  9522. <b>Hinweis: </b> <br>
  9523. In der Detailansicht kann ein Browserrefresh nötig sein um die Operationsergebnisse zu sehen sobald im DeviceOverview "state = done" angezeigt wird.
  9524. <br><br>
  9525. </ul>
  9526. <a name="DbRepget"></a>
  9527. <b>Get </b>
  9528. <ul>
  9529. Die Get-Kommandos von DbRep dienen dazu eine Reihe von Metadaten der verwendeten Datenbankinstanz abzufragen.
  9530. Dies sind zum Beispiel eingestellte Serverparameter, Servervariablen, Datenbankstatus- und Tabelleninformationen. Die verfügbaren get-Funktionen
  9531. 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,
  9532. die über über funktionsspezifische <a href="#DbRepattr">Attribute</a> abgrenzbar sind. Der Filter ist als kommaseparierte Liste anzuwenden.
  9533. Dabei kann SQL-Wildcard (%) verwendet werden.
  9534. <br><br>
  9535. <b>Hinweis: </b> <br>
  9536. Nach der Ausführung einer get-Funktion in der Detailsicht einen Browserrefresh durchführen um die Ergebnisse zu sehen !
  9537. <br><br>
  9538. <ul><ul>
  9539. <li><b> blockinginfo </b> - Listet die aktuell systemweit laufenden Hintergrundprozesse (BlockingCalls) mit ihren Informationen auf.
  9540. Zu lange Zeichenketten (z.B. Argumente) werden gekürzt ausgeschrieben.
  9541. </li>
  9542. <br><br>
  9543. <li><b> dbstatus </b> - Listet globale Informationen zum MySQL Serverstatus (z.B. Informationen zum Cache, Threads, Bufferpools, etc. ).
  9544. Es werden zunächst alle verfügbaren Informationen berichtet. Mit dem <a href="#DbRepattr">Attribut</a> "showStatus" kann die
  9545. Ergebnismenge eingeschränkt werden, um nur gewünschte Ergebnisse abzurufen. Detailinformationen zur Bedeutung der einzelnen Readings
  9546. sind <a href=http://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html>hier</a> verfügbar. <br>
  9547. <br><ul>
  9548. <b>Bespiel</b> <br>
  9549. get &lt;name&gt; dbstatus <br>
  9550. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  9551. # Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten
  9552. </li>
  9553. <br><br>
  9554. </ul>
  9555. <li><b> dbvars </b> - Zeigt die globalen Werte der MySQL Systemvariablen. Enthalten sind zum Beispiel Angaben zum InnoDB-Home, dem Datafile-Pfad,
  9556. Memory- und Cache-Parameter, usw. Die Ausgabe listet zunächst alle verfügbaren Informationen auf. Mit dem
  9557. <a href="#DbRepattr">Attribut</a> "showVariables" kann die Ergebnismenge eingeschränkt werden um nur gewünschte Ergebnisse
  9558. abzurufen. Weitere Informationen zur Bedeutung der ausgegebenen Variablen sind
  9559. <a href=http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html>hier</a> verfügbar. <br>
  9560. <br><ul>
  9561. <b>Bespiel</b> <br>
  9562. get &lt;name&gt; dbvars <br>
  9563. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  9564. # Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten
  9565. </li>
  9566. <br><br>
  9567. </ul>
  9568. <li><b> minTimestamp </b> - Ermittelt den Zeitstempel des ältesten Datensatzes in der Datenbank (wird implizit beim Start von
  9569. FHEM ausgeführt).
  9570. Der Zeitstempel wird als Selektionsbeginn verwendet wenn kein Zeitattribut den Selektionsbeginn
  9571. festlegt.
  9572. </li>
  9573. <br><br>
  9574. <li><b> procinfo </b> - Listet die existierenden Datenbank-Prozesse in einer Tabelle auf (nur MySQL). <br>
  9575. Typischerweise werden nur die Prozesse des Verbindungsusers (angegeben in DbLog-Konfiguration)
  9576. ausgegeben. Sollen alle Prozesse angezeigt werden, ist dem User das globale Recht "PROCESS"
  9577. einzuräumen. <br>
  9578. Für bestimmte SQL-Statements wird seit MariaDB 5.3 ein Fortschrittsreporting (Spalte "PROGRESS")
  9579. ausgegeben. Zum Beispiel kann der Abarbeitungsgrad bei der Indexerstellung verfolgt werden. <br>
  9580. Weitere Informationen sind
  9581. <a href=https://mariadb.com/kb/en/mariadb/show-processlist/>hier</a> verfügbar. <br>
  9582. </li>
  9583. <br><br>
  9584. <li><b> svrinfo </b> - allgemeine Datenbankserver-Informationen wie z.B. die DBMS-Version, Serveradresse und Port usw. Die Menge der Listenelemente
  9585. ist vom Datenbanktyp abhängig. Mit dem <a href="#DbRepattr">Attribut</a> "showSvrInfo" kann die Ergebnismenge eingeschränkt werden.
  9586. Weitere Erläuterungen zu den gelieferten Informationen sind
  9587. <a href=https://msdn.microsoft.com/en-us/library/ms711681(v=vs.85).aspx>hier</a> zu finden. <br>
  9588. <br><ul>
  9589. <b>Bespiel</b> <br>
  9590. get &lt;name&gt; svrinfo <br>
  9591. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  9592. # Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten
  9593. </li>
  9594. <br><br>
  9595. </ul>
  9596. <li><b> tableinfo </b> - ruft Tabelleninformationen aus der mit dem DbRep-Device verbundenen Datenbank ab (MySQL).
  9597. Es werden per default alle in der verbundenen Datenbank angelegten Tabellen ausgewertet.
  9598. Mit dem <a href="#DbRepattr">Attribut</a> "showTableInfo" können die Ergebnisse eingeschränkt werden. Erläuterungen zu den erzeugten
  9599. Readings sind <a href=http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html>hier</a> zu finden. <br>
  9600. <br><ul>
  9601. <b>Bespiel</b> <br>
  9602. get &lt;name&gt; tableinfo <br>
  9603. attr &lt;name&gt; showTableInfo current,history <br>
  9604. # Es werden nur Information der Tabellen "current" und "history" angezeigt
  9605. </li>
  9606. <br><br>
  9607. </ul>
  9608. <br>
  9609. </ul></ul>
  9610. </ul>
  9611. <a name="DbRepattr"></a>
  9612. <b>Attribute</b>
  9613. <br>
  9614. <ul>
  9615. Über die modulspezifischen Attribute wird die Abgrenzung der Auswertung und die Aggregation der Werte gesteuert. <br><br>
  9616. <b>Hinweis zur SQL-Wildcard Verwendung:</b> <br>
  9617. Innerhalb der Attribut-Werte für "device" und "reading" kann SQL-Wildcards "%" angegeben werden.
  9618. Dabei wird "%" als Platzhalter für beliebig viele Zeichen verwendet.
  9619. Das Zeichen "_" wird nicht als SQL-Wildcard supported. <br>
  9620. Dies gilt für alle Funktionen <b>ausser</b> "insert", "importFromFile" und "deviceRename". <br>
  9621. Die Funktion "insert" erlaubt nicht, dass die genannten Attribute das Wildcard "%" enthalten. Character "_" wird als normales Zeichen gewertet.<br>
  9622. In Ergebnis-Readings wird das Wildcardzeichen "%" durch "/" ersetzt um die Regeln für erlaubte Zeichen in Readings einzuhalten.
  9623. <br><br>
  9624. <ul><ul>
  9625. <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>
  9626. <li><b>allowDeletion </b> - schaltet die Löschfunktion des Moduls frei </li> <br>
  9627. <li><b>averageCalcForm </b> - legt die Berechnungsvariante für die Ermittlung des Durchschnittswertes mit "averageValue" fest.
  9628. </li><br>
  9629. <li><b>device </b> - Abgrenzung der DB-Selektionen auf ein bestimmtes Device. <br>
  9630. Es können <a href="https://fhem.de/commandref_DE.html#devspec">Geräte-Spezifikationen (devspec)</a>
  9631. angegeben werden. <br>
  9632. Innerhalb von Geräte-Spezifikationen wird SQL-Wildcard (%) als normales ASCII-Zeichen gewertet.
  9633. Die Devicenamen werden vor der Selektion aus der Geräte-Spezifikationen und den aktuell in FHEM
  9634. vorhandenen Devices abgeleitet. </li> <br>
  9635. <ul>
  9636. <b>Beispiele:</b> <br>
  9637. <code>attr &lt;name&gt; device TYPE=DbRep</code> <br>
  9638. <code>attr &lt;name&gt; device MySTP_5000</code> <br>
  9639. <code>attr &lt;name&gt; device SMA.*,MySTP.*</code> <br>
  9640. <code>attr &lt;name&gt; device SMA_Energymeter,MySTP_5000</code> <br>
  9641. <code>attr &lt;name&gt; device %5000</code> <br>
  9642. </ul>
  9643. <br><br>
  9644. <li><b>diffAccept </b> - gilt für Funktion diffValue. diffAccept legt fest bis zu welchem Schwellenwert eine berechnete positive Werte-Differenz
  9645. zwischen zwei unmittelbar aufeinander folgenden Datensätzen akzeptiert werden soll (Standard ist 20). <br>
  9646. Damit werden fehlerhafte DB-Einträge mit einem unverhältnismäßig hohen Differenzwert von der Berechnung ausgeschlossen und
  9647. verfälschen nicht das Ergebnis. Sollten Schwellenwertüberschreitungen vorkommen, wird das Reading "diff_overrun_limit_&lt;diffLimit&gt;"
  9648. erstellt. (&lt;diffLimit&gt; wird dabei durch den aktuellen Attributwert ersetzt)
  9649. Es enthält eine Liste der relevanten Wertepaare. Mit verbose 3 werden diese Datensätze ebenfalls im Logfile protokolliert.
  9650. </li> <br>
  9651. <ul>
  9652. Beispiel Ausgabe im Logfile beim Überschreiten von diffAccept=10: <br><br>
  9653. DbRep Rep.STP5000.etotal -> data ignored while calc diffValue due to threshold overrun (diffAccept = 10): <br>
  9654. 2016-04-09 08:50:50 0.0340 -> 2016-04-09 12:42:01 13.3440 <br><br>
  9655. # 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
  9656. Differenzwert. <br>
  9657. # Es ist zu entscheiden ob der Datensatz gelöscht, ignoriert, oder das Attribut diffAccept angepasst werden sollte.
  9658. </ul><br>
  9659. <li><b>disable </b> - deaktiviert das Modul </li> <br>
  9660. <li><b>dumpComment </b> - User-Kommentar. Er wird im Kopf des durch den Befehl "dumpMyQL clientSide" erzeugten Dumpfiles
  9661. eingetragen. </li> <br>
  9662. <li><b>dumpCompress </b> - wenn gesetzt, werden die Dumpfiles nach "dumpMySQL" bzw. "dumpSQLite" komprimiert </li> <br>
  9663. <li><b>dumpDirLocal </b> - Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL clientSide".
  9664. default: "{global}{modpath}/log/" auf dem FHEM-Server. <br>
  9665. Ebenfalls werden in diesem Verzeichnis alte Backup-Files durch die interne Versionsverwaltung von
  9666. "dumpMySQL" gesucht und gelöscht wenn die gefundene Anzahl den Attributwert "dumpFilesKeep"
  9667. überschreitet. Das Attribut dient auch dazu ein lokal gemountetes Verzeichnis "dumpDirRemote"
  9668. DbRep bekannt zu machen. </li> <br>
  9669. <li><b>dumpDirRemote </b> - Zielverzeichnis für die Erstellung von Dumps mit "dumpMySQL serverSide".
  9670. default: das Home-Dir des MySQL-Servers auf dem MySQL-Host </li> <br>
  9671. <li><b>dumpMemlimit </b> - erlaubter Speicherverbrauch für SQL-Script zur Generierungszeit (default: 100000 Zeichen).
  9672. Bitte den Parameter anpassen, falls es zu Speicherengpässen und damit verbundenen Performanceproblemen
  9673. kommen sollte. </li> <br>
  9674. <li><b>dumpSpeed </b> - Anzahl der abgerufenen Zeilen aus der Quelldatenbank (default: 10000) pro Select durch "dumpMySQL ClientSide".
  9675. Dieser Parameter hat direkten Einfluß auf die Laufzeit und den Ressourcenverbrauch zur Laufzeit. </li> <br>
  9676. <li><b>dumpFilesKeep </b> - Es wird die angegebene Anzahl Dumpfiles im Dumpdir belassen (default: 3). Sind mehr (ältere) Dumpfiles
  9677. vorhanden, werden diese gelöscht nachdem ein neuer Dump erfolgreich erstellt wurde. Das globale
  9678. Attribut "archivesort" wird berücksichtigt. </li> <br>
  9679. <li><b>executeAfterProc </b> - Es kann ein FHEM-Kommando angegeben werden welches <b>nach dem Dump</b> ausgeführt werden soll. <br>
  9680. Funktionen sind in {} einzuschließen.<br><br>
  9681. <ul>
  9682. <b>Beispiel:</b> <br><br>
  9683. attr &lt;name&gt; executeAfterProc set og_gz_westfenster off; <br>
  9684. attr &lt;name&gt; executeAfterProc {adump ("&lt;name&gt;")} <br><br>
  9685. # "adump" ist eine in 99_myUtils definierte Funktion. <br>
  9686. <pre>
  9687. sub adump {
  9688. my ($name) = @_;
  9689. my $hash = $defs{$name};
  9690. # die eigene Funktion, z.B.
  9691. Log3($name, 3, "DbRep $name -> Dump ist beendet");
  9692. return;
  9693. }
  9694. </pre>
  9695. </ul>
  9696. </li>
  9697. <li><b>executeBeforeProc </b> - Es kann ein FHEM-Kommando angegeben werden welches <b>vor dem Dump</b> ausgeführt werden soll. <br>
  9698. Funktionen sind in {} einzuschließen.<br><br>
  9699. <ul>
  9700. <b>Beispiel:</b> <br><br>
  9701. attr &lt;name&gt; executeBeforeProc set og_gz_westfenster on; <br>
  9702. attr &lt;name&gt; executeBeforeProc {bdump ("&lt;name&gt;")} <br><br>
  9703. # "bdump" ist eine in 99_myUtils definierte Funktion. <br>
  9704. <pre>
  9705. sub bdump {
  9706. my ($name) = @_;
  9707. my $hash = $defs{$name};
  9708. # die eigene Funktion, z.B.
  9709. Log3($name, 3, "DbRep $name -> Dump startet");
  9710. return;
  9711. }
  9712. </pre>
  9713. </ul>
  9714. </li>
  9715. <li><b>expimpfile </b> - Pfad/Dateiname für Export/Import in/aus einem File. </li> <br>
  9716. <li><b>fetchMarkDuplicates </b>
  9717. - Markierung von mehrfach vorkommenden Datensätzen im Ergebnis des "fetchrows" Kommandos </li> <br>
  9718. <li><b>fetchRoute [descent | ascent] </b> - bestimmt die Leserichtung des fetchrows-Befehl. <br><br>
  9719. <ul>
  9720. <b>descent</b> - die Datensätze werden absteigend gelesen (default). Wird
  9721. die durch das Attribut "limit" festgelegte Anzahl der Datensätze
  9722. überschritten, werden die neuesten x Datensätze angezeigt. <br><br>
  9723. <b>ascent</b> - die Datensätze werden aufsteigend gelesen. Wird
  9724. die durch das Attribut "limit" festgelegte Anzahl der Datensätze
  9725. überschritten, werden die ältesten x Datensätze angezeigt. <br>
  9726. </ul>
  9727. </li> <br><br>
  9728. <li><b>ftpUse </b> - FTP Transfer nach dem Dump wird eingeschaltet (ohne SSL Verschlüsselung). Das erzeugte
  9729. Datenbank Backupfile wird non-blocking zum angegebenen FTP-Server (Attribut "ftpServer")
  9730. übertragen. </li> <br>
  9731. <li><b>ftpUseSSL </b> - FTP Transfer mit SSL Verschlüsselung nach dem Dump wird eingeschaltet. Das erzeugte
  9732. Datenbank Backupfile wird non-blocking zum angegebenen FTP-Server (Attribut "ftpServer")
  9733. übertragen. </li> <br>
  9734. <li><b>ftpUser </b> - User zur Anmeldung am FTP-Server, default: "anonymous". </li> <br>
  9735. <li><b>ftpDebug </b> - Debugging der FTP Kommunikation zur Fehlersuche. </li> <br>
  9736. <li><b>ftpDir </b> - Verzeichnis des FTP-Servers in welches das File übertragen werden soll (default: "/"). </li> <br>
  9737. <li><b>ftpDumpFilesKeep </b> - Es wird die angegebene Anzahl Dumpfiles im &lt;ftpDir&gt; belassen (default: 3). Sind mehr
  9738. (ältere) Dumpfiles vorhanden, werden diese gelöscht nachdem ein neuer Dump erfolgreich
  9739. übertragen wurde. </li> <br>
  9740. <li><b>ftpPassive </b> - setzen wenn passives FTP verwendet werden soll </li> <br>
  9741. <li><b>ftpPort </b> - FTP-Port, default: 21 </li> <br>
  9742. <li><b>ftpPwd </b> - Passwort des FTP-Users, default nicht gesetzt </li> <br>
  9743. <li><b>ftpServer </b> - Name oder IP-Adresse des FTP-Servers. <b>notwendig !</b> </li> <br>
  9744. <li><b>ftpTimeout </b> - Timeout für die FTP-Verbindung in Sekunden (default: 30). </li> <br>
  9745. <a name="DbRepattrlimit"></a>
  9746. <li><b>limit </b> - begrenzt die Anzahl der resultierenden Datensätze im select-Statement von "fetchrows", bzw. der anzuzeigenden Datensätze
  9747. der Kommandos "delSeqDoublets adviceDelete", "delSeqDoublets adviceRemain" (default 1000).
  9748. Diese Limitierung soll eine Überlastung der Browsersession und ein
  9749. blockieren von FHEMWEB verhindern. Bei Bedarf entsprechend ändern bzw. die
  9750. Selektionskriterien (Zeitraum der Auswertung) anpassen. </li> <br>
  9751. <li><b>optimizeTablesBeforeDump </b> - wenn "1", wird vor dem Datenbankdump eine Tabellenoptimierung ausgeführt (default: 0).
  9752. Dadurch verlängert sich die Laufzeit des Dump. <br><br>
  9753. <ul>
  9754. <b>Hinweis </b> <br>
  9755. Die Tabellenoptimierung führt zur Sperrung der Tabellen und damit zur Blockierung von
  9756. FHEM falls DbLog nicht im asynchronen Modus (DbLog-Attribut "asyncMode") betrieben wird !
  9757. <br>
  9758. </ul>
  9759. </li> <br>
  9760. <li><b>reading </b> - Abgrenzung der DB-Selektionen auf ein bestimmtes oder mehrere Readings.
  9761. Mehrere Readings werden als Komma separierte Liste angegeben. <br>
  9762. SQL Wildcard (%) wird in einer Liste als normales ASCII-Zeichen gewertet. <br>
  9763. </li> <br>
  9764. <ul>
  9765. <b>Beispiele:</b> <br>
  9766. <code>attr &lt;name&gt; reading etotal</code> <br>
  9767. <code>attr &lt;name&gt; reading et%</code> <br>
  9768. <code>attr &lt;name&gt; reading etotal,etoday</code> <br>
  9769. </ul>
  9770. <br><br>
  9771. <li><b>readingNameMap </b> - der Name des ausgewerteten Readings wird mit diesem String für die Anzeige überschrieben </li> <br>
  9772. <li><b>readingPreventFromDel </b> - Komma separierte Liste von Readings die vor einer neuen Operation nicht gelöscht
  9773. werden sollen </li> <br>
  9774. <li><b>role </b> - die Rolle des DbRep-Device. Standard ist "Client". Die Rolle "Agent" ist im Abschnitt
  9775. <a href="#DbRepAutoRename">DbRep-Agent</a> beschrieben. </li> <br>
  9776. <li><b>seqDoubletsVariance </b> - akzeptierte Abweichung (+/-) für das Kommando "set &lt;name&gt; delSeqDoublets". <br>
  9777. Der Wert des Attributs beschreibt die Abweichung bis zu der aufeinanderfolgende numerische Werte (VALUE) von
  9778. Datensätze als gleich angesehen und gelöscht werden sollen. "seqDoubletsVariance" ist ein absoluter Zahlenwert,
  9779. der sowohl als positive als auch negative Abweichung verwendet wird. </li> <br>
  9780. <ul>
  9781. <b>Beispiele:</b> <br>
  9782. <code>attr &lt;name&gt; seqDoubletsVariance 0.0014 </code> <br>
  9783. <code>attr &lt;name&gt; seqDoubletsVariance 1.45 </code> <br>
  9784. </ul>
  9785. <br><br>
  9786. <li><b>showproctime </b> - wenn gesetzt, zeigt das Reading "sql_processing_time" die benötigte Abarbeitungszeit (in Sekunden)
  9787. für die SQL-Ausführung der durchgeführten Funktion. Dabei wird nicht ein einzelnes
  9788. SQl-Statement, sondern die Summe aller notwendigen SQL-Abfragen innerhalb der jeweiligen
  9789. Funktion betrachtet. </li> <br>
  9790. <li><b>showStatus </b> - grenzt die Ergebnismenge des Befehls "get &lt;name&gt; dbstatus" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  9791. <ul>
  9792. <b>Bespiel: </b> <br>
  9793. attr &lt;name&gt; showStatus %uptime%,%qcache% <br>
  9794. # Es werden nur Readings erzeugt die im Namen "uptime" und "qcache" enthalten <br>
  9795. </ul><br>
  9796. <li><b>showVariables </b> - grenzt die Ergebnismenge des Befehls "get &lt;name&gt; dbvars" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  9797. <ul>
  9798. <b>Bespiel: </b> <br>
  9799. attr &lt;name&gt; showVariables %version%,%query_cache% <br>
  9800. # Es werden nur Readings erzeugt die im Namen "version" und "query_cache" enthalten <br>
  9801. </ul><br>
  9802. <li><b>showSvrInfo </b> - grenzt die Ergebnismenge des Befehls "get &lt;name&gt; svrinfo" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  9803. <ul>
  9804. <b>Bespiel: </b> <br>
  9805. attr &lt;name&gt; showSvrInfo %SQL_CATALOG_TERM%,%NAME% <br>
  9806. # Es werden nur Readings erzeugt die im Namen "SQL_CATALOG_TERM" und "NAME" enthalten <br>
  9807. </ul><br>
  9808. <li><b>showTableInfo </b> - grenzt die Ergebnismenge des Befehls "get &lt;name&gt; tableinfo" ein. Es können SQL-Wildcard (%) verwendet werden. </li> <br>
  9809. <ul>
  9810. <b>Bespiel: </b> <br>
  9811. attr &lt;name&gt; showTableInfo current,history <br>
  9812. # Es werden nur Information der Tabellen "current" und "history" angezeigt <br>
  9813. </ul><br>
  9814. <li><b>sqlResultFieldSep </b> - legt den verwendeten Feldseparator (default: "|") im Ergebnis des Kommandos "set ... sqlCmd" fest. </li> <br>
  9815. <li><b>sqlCmdHistoryLength </b>
  9816. - aktiviert die Kommandohistorie von "sqlCmd" und legt deren Länge fest </li> <br>
  9817. <li><b>sqlResultFormat </b> - legt die Formatierung des Ergebnisses des Kommandos "set &lt;name&gt; sqlCmd" fest.
  9818. Mögliche Optionen sind: <br><br>
  9819. <ul>
  9820. <b>separated </b> - die Ergebniszeilen werden als einzelne Readings fortlaufend
  9821. generiert. (default)<br><br>
  9822. <b>mline </b> - das Ergebnis wird als Mehrzeiler im <a href="#DbRepReadings">Reading</a>
  9823. SqlResult dargestellt. <br><br>
  9824. <b>sline </b> - das Ergebnis wird als Singleline im <a href="#DbRepReadings">Reading</a>
  9825. SqlResult dargestellt. Satztrenner ist"]|[". <br><br>
  9826. <b>table </b> - das Ergebnis wird als Tabelle im <a href="#DbRepReadings">Reading</a>
  9827. SqlResult dargestellt. <br><br>
  9828. <b>json </b> - erzeugt das <a href="#DbRepReadings">Reading</a> SqlResult als
  9829. JSON-kodierten Hash.
  9830. Jedes Hash-Element (Ergebnissatz) setzt sich aus der laufenden Nummer
  9831. des Datensatzes (Key) und dessen Wert zusammen. </li><br><br>
  9832. Die Weiterverarbeitung des Ergebnisses kann z.B. mit der folgenden userExitFn in 99_myUtils.pm erfolgen: <br>
  9833. <pre>
  9834. sub resfromjson {
  9835. my ($name,$reading,$value) = @_;
  9836. my $hash = $defs{$name};
  9837. if ($reading eq "SqlResult") {
  9838. # nur Reading SqlResult enthält JSON-kodierte Daten
  9839. my $data = decode_json($value);
  9840. foreach my $k (keys(%$data)) {
  9841. # ab hier eigene Verarbeitung für jedes Hash-Element
  9842. # z.B. Ausgabe jedes Element welches "Cam" enthält
  9843. my $ke = $data->{$k};
  9844. if($ke =~ m/Cam/i) {
  9845. my ($res1,$res2) = split("\\|", $ke);
  9846. Log3($name, 1, "$name - extract element $k by userExitFn: ".$res1." ".$res2);
  9847. }
  9848. }
  9849. }
  9850. return;
  9851. }
  9852. </pre>
  9853. </ul><br>
  9854. <li><b>timeYearPeriod </b> - Mit Hilfe dieses Attributes wird eine jährliche Zeitperiode für die Datenbankselektion bestimmt.
  9855. Die Zeitgrenzen werden zur Ausführungszeit dynamisch berechnet. Es wird immer eine Jahresperiode
  9856. bestimmt. Eine unterjährige Angabe ist nicht möglich. <br>
  9857. Dieses Attribut ist vor allem dazu gedacht Auswertungen synchron zu einer Abrechnungsperiode, z.B. der eines
  9858. Energie- oder Gaslieferanten, anzufertigen.
  9859. </li> <br>
  9860. <ul>
  9861. <b>Beispiel:</b> <br><br>
  9862. attr &lt;name&gt; timeYearPeriod 06-25 06-24 <br><br>
  9863. # wertet die Datenbank in den Zeitgrenzen 25. Juni AAAA bis 24. Juni BBBB aus. <br>
  9864. # Das Jahr AAAA bzw. BBBB wird in Abhängigkeit des aktuellen Datums errechnet. <br>
  9865. # Ist das aktuelle Datum >= 25. Juni und =< 31. Dezember, dann ist AAAA = aktuelles Jahr und BBBB = aktuelles Jahr+1 <br>
  9866. # Ist das aktuelle Datum >= 01. Januar und =< 24. Juni, dann ist AAAA = aktuelles Jahr-1 und BBBB = aktuelles Jahr
  9867. </ul>
  9868. <br><br>
  9869. <li><b>timestamp_begin </b> - der zeitliche Beginn für die Datenselektion (*) </li> <br>
  9870. <li><b>timestamp_end </b> - das zeitliche Ende für die Datenselektion. Wenn nicht gesetzt wird immer die aktuelle
  9871. Datum/Zeit-Kombi für das Ende der Selektion eingesetzt. (*) </li> <br>
  9872. (*) Das Format von Timestamp ist wie in DbLog "YYYY-MM-DD HH:MM:SS". Für die Attribute "timestamp_begin", "timestamp_end"
  9873. kann ebenso eine der folgenden Eingaben verwendet werden. Dabei wird das timestamp-Attribut dynamisch belegt: <br><br>
  9874. <ul>
  9875. <b>current_year_begin</b> : entspricht "&lt;aktuelles Jahr&gt;-01-01 00:00:00" <br>
  9876. <b>current_year_end</b> : entspricht "&lt;aktuelles Jahr&gt;-12-31 23:59:59" <br>
  9877. <b>previous_year_begin</b> : entspricht "&lt;vorheriges Jahr&gt;-01-01 00:00:00" <br>
  9878. <b>previous_year_end</b> : entspricht "&lt;vorheriges Jahr&gt;-12-31 23:59:59" <br>
  9879. <b>current_month_begin</b> : entspricht "&lt;aktueller Monat erster Tag&gt; 00:00:00" <br>
  9880. <b>current_month_end</b> : entspricht "&lt;aktueller Monat letzter Tag&gt; 23:59:59" <br>
  9881. <b>previous_month_begin</b> : entspricht "&lt;Vormonat erster Tag&gt; 00:00:00" <br>
  9882. <b>previous_month_end</b> : entspricht "&lt;Vormonat letzter Tag&gt; 23:59:59" <br>
  9883. <b>current_week_begin</b> : entspricht "&lt;erster Tag der akt. Woche&gt; 00:00:00" <br>
  9884. <b>current_week_end</b> : entspricht "&lt;letzter Tag der akt. Woche&gt; 23:59:59" <br>
  9885. <b>previous_week_begin</b> : entspricht "&lt;erster Tag Vorwoche&gt; 00:00:00" <br>
  9886. <b>previous_week_end</b> : entspricht "&lt;letzter Tag Vorwoche&gt; 23:59:59" <br>
  9887. <b>current_day_begin</b> : entspricht "&lt;aktueller Tag&gt; 00:00:00" <br>
  9888. <b>current_day_end</b> : entspricht "&lt;aktueller Tag&gt; 23:59:59" <br>
  9889. <b>previous_day_begin</b> : entspricht "&lt;Vortag&gt; 00:00:00" <br>
  9890. <b>previous_day_end</b> : entspricht "&lt;Vortag&gt; 23:59:59" <br>
  9891. <b>current_hour_begin</b> : entspricht "&lt;aktuelle Stunde&gt;:00:00" <br>
  9892. <b>current_hour_end</b> : entspricht "&lt;aktuelle Stunde&gt;:59:59" <br>
  9893. <b>previous_hour_begin</b> : entspricht "&lt;vorherige Stunde&gt;:00:00" <br>
  9894. <b>previous_hour_end</b> : entspricht "&lt;vorherige Stunde&gt;:59:59" <br>
  9895. </ul><br>
  9896. Natürlich sollte man immer darauf achten dass "timestamp_begin" < "timestamp_end" ist. <br><br>
  9897. <ul>
  9898. <b>Beispiel:</b> <br><br>
  9899. attr &lt;name&gt; timestamp_begin current_year_begin <br>
  9900. attr &lt;name&gt; timestamp_end current_year_end <br><br>
  9901. # Wertet die Datenbank in den Zeitgrenzen des aktuellen Jahres aus. <br>
  9902. </ul>
  9903. <br><br>
  9904. <b>Hinweis </b> <br>
  9905. Wird das Attribut "timeDiffToNow" gesetzt, werden die eventuell gesetzten anderen Zeit-Attribute
  9906. ("timestamp_begin","timestamp_end","timeYearPeriod") gelöscht.
  9907. Das Setzen von "timestamp_begin" bzw. "timestamp_end" bedingt die Löschung von anderen Zeit-Attribute falls sie vorher gesetzt waren.
  9908. <br><br>
  9909. <li><b>timeDiffToNow </b> - der <b>Selektionsbeginn</b> wird auf den Zeitpunkt <b>"&lt;aktuelle Zeit&gt; - &lt;timeDiffToNow&gt;"</b>
  9910. gesetzt (z.b. werden die letzten 24 Stunden in die Selektion eingehen wenn das Attribut auf "86400" gesetzt
  9911. wurde). Die Timestampermittlung erfolgt dynamisch zum Ausführungszeitpunkt. </li> <br>
  9912. <ul>
  9913. <b>Eingabeformat Beispiel:</b> <br>
  9914. <code>attr &lt;name&gt; timeDiffToNow 86400</code> <br>
  9915. # die Startzeit wird auf "aktuelle Zeit - 86400 Sekunden" gesetzt <br>
  9916. <code>attr &lt;name&gt; timeDiffToNow d:2 h:3 m:2 s:10</code> <br>
  9917. # die Startzeit wird auf "aktuelle Zeit - 2 Tage 3 Stunden 2 Minuten 10 Sekunden" gesetzt <br>
  9918. <code>attr &lt;name&gt; timeDiffToNow m:600</code> <br>
  9919. # die Startzeit wird auf "aktuelle Zeit - 600 Minuten" gesetzt <br>
  9920. <code>attr &lt;name&gt; timeDiffToNow h:2.5</code> <br>
  9921. # die Startzeit wird auf "aktuelle Zeit - 2,5 Stunden" gesetzt <br>
  9922. </ul>
  9923. <br><br>
  9924. <li><b>timeOlderThan </b> - das <b>Selektionsende</b> wird auf den Zeitpunkt <b>"&lt;aktuelle Zeit&gt; - &lt;timeOlderThan&gt;"</b>
  9925. gesetzt. Dadurch werden alle Datensätze bis zu dem Zeitpunkt "&lt;aktuelle
  9926. Zeit&gt; - &lt;timeOlderThan&gt;" berücksichtigt (z.b. wenn auf 86400 gesetzt, werden alle
  9927. Datensätze die älter als ein Tag sind berücksichtigt). Die Timestampermittlung erfolgt
  9928. dynamisch zum Ausführungszeitpunkt. <br>
  9929. Es gelten die gleichen Eingabeformate wie für das Attribut "timeDiffToNow". </li> <br>
  9930. <li><b>timeout </b> - das Attribut setzt den Timeout-Wert für die Blocking-Call Routinen in Sekunden
  9931. (Default: 86400) </li> <br>
  9932. <li><b>userExitFn </b> - stellt eine Schnittstelle zur Ausführung eigenen Usercodes zur Verfügung. <br>
  9933. Um die Schnittstelle zu aktivieren, wird zunächst die aufzurufende Subroutine in
  9934. 99_myUtls.pm nach folgendem Muster erstellt: <br>
  9935. <pre>
  9936. sub UserFunction {
  9937. my ($name,$reading,$value) = @_;
  9938. my $hash = $defs{$name};
  9939. ...
  9940. # z.B. übergebene Daten loggen
  9941. Log3 $name, 1, "UserExitFn $name called - transfer parameter are Reading: $reading, Value: $value " ;
  9942. ...
  9943. return;
  9944. }
  9945. </pre>
  9946. Die Aktivierung der Schnittstelle erfogt durch Setzen des Funktionsnamens im Attribut.
  9947. Optional kann ein Reading:Value Regex als Argument angegeben werden. Wird kein Regex
  9948. angegeben, werden alle Wertekombinationen als "wahr" gewertet (entspricht .*:.*).
  9949. <br><br>
  9950. <ul>
  9951. <b>Beispiel:</b> <br>
  9952. attr <device> userExitFn UserFunction .*:.* <br>
  9953. # "UserFunction" ist die Subroutine in 99_myUtils.pm.
  9954. </ul>
  9955. <br>
  9956. Grundsätzlich arbeitet die Schnittstelle OHNE Eventgenerierung bzw. benötigt zur Funktion keinen
  9957. Event. Sofern das Attribut gesetzt ist, erfolgt Die Regexprüfung NACH der Erstellung eines
  9958. Readings. Ist die Prüfung WAHR, wird die angegebene Funktion aufgerufen.
  9959. Zur Weiterverarbeitung werden der aufgerufenenen Funktion folgende Variablen übergeben: <br><br>
  9960. <ul>
  9961. <li>$name - der Name des DbRep-Devices </li>
  9962. <li>$reading - der Namen des erstellen Readings </li>
  9963. <li>$value - der Wert des Readings </li>
  9964. </ul>
  9965. </li>
  9966. <br><br>
  9967. </ul></ul>
  9968. </ul>
  9969. <a name="DbRepReadings"></a>
  9970. <b>Readings</b>
  9971. <br>
  9972. <ul>
  9973. Abhängig von der ausgeführten DB-Operation werden die Ergebnisse in entsprechenden Readings dargestellt. Zu Beginn einer neuen Operation
  9974. werden alle alten Readings einer vorangegangenen Operation gelöscht um den Verbleib unpassender bzw. ungültiger Readings zu vermeiden.
  9975. <br><br>
  9976. Zusätzlich werden folgende Readings erzeugt (Auswahl): <br><br>
  9977. <ul><ul>
  9978. <li><b>state </b> - enthält den aktuellen Status der Auswertung. Wenn Warnungen auftraten (state = Warning) vergleiche Readings
  9979. "diff_overrun_limit_&lt;diffLimit&gt;" und "less_data_in_period" </li> <br>
  9980. <li><b>errortext </b> - Grund eines Fehlerstatus </li> <br>
  9981. <li><b>background_processing_time </b> - die gesamte Prozesszeit die im Hintergrund/Blockingcall verbraucht wird </li> <br>
  9982. <li><b>diff_overrun_limit_&lt;diffLimit&gt;</b> - enthält eine Liste der Wertepaare die eine durch das Attribut "diffAccept" festgelegte Differenz
  9983. &lt;diffLimit&gt; (Standard: 20) überschreiten. Gilt für Funktion "diffValue". </li> <br>
  9984. <li><b>less_data_in_period </b> - enthält eine Liste der Zeitperioden in denen nur ein einziger Datensatz gefunden wurde. Die
  9985. Differenzberechnung berücksichtigt den letzten Wert der Vorperiode. Gilt für Funktion "diffValue". </li> <br>
  9986. <li><b>sql_processing_time </b> - der Anteil der Prozesszeit die für alle SQL-Statements der ausgeführten
  9987. Operation verbraucht wird </li> <br>
  9988. <li><b>SqlResult </b> - Ergebnis des letzten sqlCmd-Kommandos. Die Formatierung erfolgt entsprechend
  9989. des <a href="#DbRepattr">Attributes</a> "sqlResultFormat" </li> <br>
  9990. <li><b>sqlCmd </b> - das letzte ausgeführte sqlCmd-Kommando </li> <br>
  9991. </ul></ul>
  9992. <br>
  9993. </ul>
  9994. <a name="DbRepAutoRename"></a>
  9995. <b>DbRep Agent - automatisches Ändern von Device-Namen in Datenbanken und DbRep-Definitionen nach FHEM "rename" Kommando</b>
  9996. <br>
  9997. <ul>
  9998. 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
  9999. veranlasst auf Umbenennungen von Geräten in der FHEM Installation zu reagieren. <br><br>
  10000. Durch den DbRep-Agenten werden folgende Features aktiviert wenn ein Gerät in FHEM mit "rename" umbenannt wird: <br><br>
  10001. <ul><ul>
  10002. <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
  10003. <b>allen</b> betroffenen Datensätzen in den neuen Namen geändert. </li> <br>
  10004. <li> in dem DbRep-Agenten zugeordneten DbLog-Device wird in der Definition das alte durch das umbenannte Device ersetzt. Dadurch erfolgt ein weiteres Logging
  10005. des umbenannten Device in der Datenbank. </li> <br>
  10006. <li> in den existierenden DbRep-Definitionen vom Typ "Client" wird ein evtl. gesetztes Attribut "device = alter Devicename" in "device = neuer Devicename"
  10007. geändert. Dadurch werden Auswertungsdefinitionen bei Geräteumbenennungen automatisch konstistent gehalten. </li> <br>
  10008. </ul></ul>
  10009. Mit der Änderung in einen Agenten sind folgende Restriktionen verbunden die mit dem Setzen des Attributes "role = Agent" eingeschaltet
  10010. und geprüft werden: <br><br>
  10011. <ul><ul>
  10012. <li> es kann nur einen Agenten pro Datenbank in der FHEM-Installation geben. Ist mehr als eine Datenbank mit DbLog definiert, können
  10013. ebenso viele DbRep-Agenten eingerichtet werden </li> <br>
  10014. <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
  10015. Attributen zugelassen. Wird ein DbRep-Device vom bisherigen Typ "Client" in einen Agenten geändert, werden evtl. gesetzte und nun nicht mehr zugelassene
  10016. Attribute glöscht. </li> <br>
  10017. </ul></ul>
  10018. Die Aktivitäten wie Datenbankänderungen bzw. Änderungen an anderen DbRep-Definitionen werden im Logfile mit verbose=3 protokolliert. Damit die renameDevice-Funktion
  10019. bei großen Datenbanken nicht in ein timeout läuft, sollte das Attribut "timeout" entsprechend dimensioniert werden. Wie alle Datenbankoperationen des Moduls
  10020. wird auch das Autorename nonblocking ausgeführt. <br><br>
  10021. <ul>
  10022. <b>Beispiel </b> für die Definition eines DbRep-Device als Agent: <br><br>
  10023. <code>
  10024. define Rep.Agent DbRep LogDB <br>
  10025. attr Rep.Agent devStateIcon connected:10px-kreis-gelb .*disconnect:10px-kreis-rot .*done:10px-kreis-gruen <br>
  10026. attr Rep.Agent icon security <br>
  10027. attr Rep.Agent role Agent <br>
  10028. attr Rep.Agent room DbLog <br>
  10029. attr Rep.Agent showproctime 1 <br>
  10030. 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>
  10031. attr Rep.Agent timeout 86400 <br>
  10032. </code>
  10033. <br>
  10034. </ul>
  10035. <b>Hinweis:</b> <br>
  10036. Obwohl die Funktion selbst non-blocking ausgelegt ist, sollte das zugeordnete DbLog-Device
  10037. im asynchronen Modus betrieben werden um ein Blockieren von FHEMWEB zu vermeiden (Tabellen-Lock). <br><br>
  10038. </ul>
  10039. =end html_DE
  10040. =cu