93_DbLog.pm 284 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302
  1. ############################################################################################################################################
  2. # $Id: 93_DbLog.pm 15449 2017-11-18 20:23:30Z DS_Starter $
  3. #
  4. # 93_DbLog.pm
  5. # written by Dr. Boris Neubert 2007-12-30
  6. # e-mail: omega at online dot de
  7. #
  8. # modified and maintained by Tobias Faust since 2012-06-26
  9. # e-mail: tobias dot faust at online dot de
  10. #
  11. # reduceLog() created by Claudiu Schuster (rapster)
  12. #
  13. # redesigned 2016/2017 by DS_Starter with credits by
  14. # JoeAllb, DeeSpe
  15. #
  16. ############################################################################################################################################
  17. # Versions History done by DS_Starter & DeeSPe:
  18. #
  19. # 2.22.14 18.11.2017 create state-events if state has been changed (Forum:#78867)
  20. # 2.22.13 20.10.2017 output of reopen command improved
  21. # 2.22.12 19.10.2017 avoid illegible messages in "state"
  22. # 2.22.11 13.10.2017 DbLogType expanded by SampleFill, DbLog_sampleDataFn adapted to sort case insensitive, commandref revised
  23. # 2.22.10 04.10.2017 Encode::encode_utf8 of $error, DbLog_PushAsyncAborted adapted to use abortArg (Forum:77472)
  24. # 2.22.9 04.10.2017 added hint to SVG/DbRep in commandref
  25. # 2.22.8 29.09.2017 avoid multiple entries in Dopdown-list when creating SVG by group Device:Reading in DbLog_sampleDataFn
  26. # 2.22.7 24.09.2017 minor fixes in configcheck
  27. # 2.22.6 22.09.2017 commandref revised
  28. # 2.22.5 05.09.2017 fix Internal MODE isn't set correctly after DEF is edited, nextsynch is not renewed if reopen is
  29. # set manually after reopen was set with a delay Forum:#76213, Link to 98_FileLogConvert.pm added
  30. # 2.22.4 27.08.2017 fhem chrashes if database DBD driver is not installed (Forum:#75894)
  31. # 2.22.3 11.08.2017 Forum:#74690, bug unitialized in row 4322 -> $ret .= SVG_txt("par_${r}_0", "", "$f0:$f1:$f2:$f3", 20);
  32. # 2.22.2 08.08.2017 Forum:#74690, bug unitialized in row 737 -> $ret .= ($fld[0]?$fld[0]:" ").'.'.($fld[1]?$fld[1]:" ");
  33. # 2.22.1 07.08.2017 attribute "suppressAddLogV3" to suppress verbose3-logentries created by DbLog_AddLog
  34. # 2.22.0 25.07.2017 attribute "addStateEvent" added
  35. # 2.21.3 24.07.2017 commandref revised
  36. # 2.21.2 19.07.2017 changed readCfg to report more error-messages
  37. # 2.21.1 18.07.2017 change configCheck for DbRep Report_Idx
  38. # 2.21.0 17.07.2017 standard timeout increased to 86400, enhanced explaination in configCheck
  39. # 2.20.0 15.07.2017 state-Events complemented with state by using $events = deviceEvents($dev_hash,1)
  40. # 2.19.0 11.07.2017 replace {DBMODEL} by {MODEL} completely
  41. # 2.18.3 04.07.2017 bugfix (links with $FW_ME deleted), MODEL as Internal (for statistic)
  42. # 2.18.2 29.06.2017 check of index for DbRep added
  43. # 2.18.1 25.06.2017 DbLog_configCheck/ DbLog_sqlget some changes, commandref revised
  44. # 2.18.0 24.06.2017 configCheck added (MySQL, PostgreSQL)
  45. # 2.17.1 17.06.2017 fix log-entries "utf8 enabled" if SVG's called, commandref revised, enable UTF8 for DbLog_get
  46. # 2.17.0 15.06.2017 enable UTF8 for MySQL (entry in configuration file necessary)
  47. # 2.16.11 03.06.2017 execmemcache changed for SQLite avoid logging if deleteOldDaysNbl or reduceLogNbL is running
  48. # 2.16.10 15.05.2017 commandref revised
  49. # 2.16.9.1 11.05.2017 set userCommand changed -
  50. # Forum: https://forum.fhem.de/index.php/topic,71808.msg633607.html#msg633607
  51. # 2.16.9 07.05.2017 addlog syntax changed to "addLog devspec:Reading [Value]"
  52. # 2.16.8 06.05.2017 in valueFN $VALUE and $UNIT can now be set to '' or 0
  53. # 2.16.7 20.04.2017 fix $now at addLog
  54. # 2.16.6 18.04.2017 AddLog set lasttime, lastvalue of dev_name, dev_reading
  55. # 2.16.5 16.04.2017 checkUsePK changed again, new attribute noSupportPK
  56. # 2.16.4 15.04.2017 commandref completed, checkUsePK changed (@usepkh = "", @usepkc = "")
  57. # 2.16.3 07.04.2017 evaluate reading in DbLog_AddLog as regular expression
  58. # 2.16.2 06.04.2017 sub DbLog_cutCol for cutting fields to maximum length, return to "$lv = "" if(!$lv);" because
  59. # of problems with MinIntervall, DbLogType-Logging in database cycle verbose 5, make $TIMESTAMP
  60. # changable by valueFn
  61. # 2.16.1 04.04.2017 changed regexp $exc =~ s/(\s|\s*\n)/,/g; , DbLog_AddLog changed, enhanced sort of listCache
  62. # 2.16.0 03.04.2017 new set-command addLog
  63. # 2.15.0 03.04.2017 new attr valueFn using for perl expression which may change variables and skip logging
  64. # unwanted datasets, change DbLog_ParseEvent for ZWAVE,
  65. # change DbLogExclude / DbLogInclude in DbLog_Log to "$lv = "" if(!defined($lv));"
  66. # 2.14.4 28.03.2017 pre-connection check in DbLog_execmemcache deleted (avoid possible blocking), attr excludeDevs
  67. # can be specified as devspec
  68. # 2.14.3 24.03.2017 DbLog_Get, DbLog_Push changed for better plotfork-support
  69. # 2.14.2 23.03.2017 new reading "lastCachefile"
  70. # 2.14.1 22.03.2017 cacheFile will be renamed after successful import by set importCachefile
  71. # 2.14.0 19.03.2017 new set-commands exportCache, importCachefile, new attr expimpdir, all cache relevant set-commands
  72. # only in drop-down list when asynch mode is used, minor fixes
  73. # 2.13.6 13.03.2017 plausibility check in set reduceLog(Nbl) enhanced, minor fixes
  74. # 2.13.5 20.02.2017 check presence of table current in DbLog_sampleDataFn
  75. # 2.13.4 18.02.2017 DbLog_Push & DbLog_PushAsync: separate eval-routines for history & current table execution
  76. # to decouple commit or rollback transactions, DbLog_sampleDataFn changed to avoid fhem from crash if table
  77. # current is not present and DbLogType isn't set
  78. # 2.13.3 18.02.2017 default timeout of DbLog_PushAsync increased to 1800,
  79. # delete {HELPER}{xx_PID} in reopen function
  80. # 2.13.2 16.02.2017 deleteOldDaysNbl added (non-blocking implementation of deleteOldDays)
  81. # 2.13.1 15.02.2017 clearReadings limited to readings which won't be recreated periodicly in asynch mode and set readings only blank,
  82. # eraseReadings added to delete readings except reading "state",
  83. # countNbl non-blocking by DeeSPe,
  84. # rename reduceLog non-blocking to reduceLogNbl and implement the old reduceLog too
  85. # 2.13.0 13.02.2017 made reduceLog non-blocking by DeeSPe
  86. # 2.12.5 11.02.2017 add support for primary key of PostgreSQL DB (Rel. 9.5) in both modes for current table
  87. # 2.12.4 09.02.2017 support for primary key of PostgreSQL DB (Rel. 9.5) in both modes only history table
  88. # 2.12.3 07.02.2017 set command clearReadings added
  89. # 2.12.2 07.02.2017 support for primary key of SQLITE DB in both modes
  90. # 2.12.1 05.02.2017 support for primary key of MySQL DB in synch mode
  91. # 2.12 04.02.2017 support for primary key of MySQL DB in asynch mode
  92. # 2.11.4 03.02.2017 check of missing modules added
  93. # 2.11.3 01.02.2017 make errorlogging of DbLog_PushAsync more identical to DbLog_Push
  94. # 2.11.2 31.01.2017 if attr colEvent, colReading, colValue is set, the limitation of fieldlength is also valid
  95. # for SQLite databases
  96. # 2.11.1 30.01.2017 output to central logfile enhanced for DbLog_Push
  97. # 2.11 28.01.2017 DbLog_connect substituted by DbLog_connectPush completely
  98. # 2.10.8 27.01.2017 setinternalcols delayed at fhem start
  99. # 2.10.7 25.01.2017 $hash->{HELPER}{COLSET} in setinternalcols, DbLog_Push changed due to
  100. # issue Turning on AutoCommit failed
  101. # 2.10.6 24.01.2017 DbLog_connect changed "connect_cashed" to "connect", DbLog_Get, chartQuery now uses
  102. # DbLog_ConnectNewDBH, Attr asyncMode changed -> delete reading cacheusage reliable if mode was switched
  103. # 2.10.5 23.01.2017 count, userCommand, deleteOldDays now uses DbLog_ConnectNewDBH
  104. # DbLog_Push line 1107 changed
  105. # 2.10.4 22.01.2017 new sub setinternalcols, new attributes colEvent, colReading, colValue
  106. # 2.10.3 21.01.2017 query of cacheEvents changed, attr timeout adjustable
  107. # 2.10.2 19.01.2017 ReduceLog now uses DbLog_ConnectNewDBH -> makes start of ReduceLog stable
  108. # 2.10.1 19.01.2017 commandref edited, cache events don't get lost even if other errors than "db not available" occure
  109. # 2.10 18.10.2017 new attribute cacheLimit, showNotifyTime
  110. # 2.9.3 17.01.2017 new sub DbLog_ConnectNewDBH (own new dbh for separate use in functions except logging functions),
  111. # DbLog_sampleDataFn, dbReadings now use DbLog_ConnectNewDBH
  112. # 2.9.2 16.01.2017 new bugfix for SQLite issue SVGs, DbLog_Log changed to $dev_hash->{CHANGETIME}, DbLog_Push
  113. # changed (db handle new separated)
  114. # 2.9.1 14.01.2017 changed DbLog_ParseEvent to CallInstanceFn, renamed flushCache to purgeCache,
  115. # renamed syncCache to commitCache, attr cacheEvents changed to 0,1,2
  116. # 2.9 11.01.2017 changed DbLog_ParseEvent to CallFn
  117. # 2.8.9 11.01.2017 own $dbhp (new DbLog_ConnectPush) for synchronous logging, delete $hash->{HELPER}{RUNNING_PID}
  118. # if DEAD, add func flushCache, syncCache
  119. # 2.8.8 10.01.2017 connection check in Get added, avoid warning "commit/rollback ineffective with AutoCommit enabled"
  120. # 2.8.7 10.01.2017 bugfix no dropdown list in SVG if asynchronous mode activated (func DbLog_sampleDataFn)
  121. # 2.8.6 09.01.2017 Workaround for Warning begin_work failed: Turning off AutoCommit failed, start new timer of
  122. # DbLog_execmemcache after reducelog
  123. # 2.8.5 08.01.2017 attr syncEvents, cacheEvents added to minimize events
  124. # 2.8.4 08.01.2017 $readingFnAttributes added
  125. # 2.8.3 08.01.2017 set NOTIFYDEV changed to use notifyRegexpChanged (Forum msg555619), attr noNotifyDev added
  126. # 2.8.2 06.01.2017 commandref maintained to cover new functions
  127. # 2.8.1 05.01.2017 use Time::HiRes qw(gettimeofday tv_interval), bugfix $hash->{HELPER}{RUNNING_PID}
  128. # 2.8 03.01.2017 attr asyncMode, you have a choice to use blocking (as V2.5) or non-blocking asynchronous
  129. # with caching, attr showproctime
  130. # 2.7 02.01.2017 initial release non-blocking using BlockingCall
  131. # 2.6 02.01.2017 asynchron writing to DB using cache, attr syncInterval, set listCache
  132. # 2.5 29.12.2016 commandref maintained to cover new attributes, attr "excludeDevs" and "verbose4Devs" now
  133. # accepting Regex
  134. # 2.4.4 28.12.2016 Attribut "excludeDevs" to exclude devices from db-logging (only if $hash->{NOTIFYDEV} eq ".*")
  135. # 2.4.3 28.12.2016 function DbLog_Log: changed separators of @row_array -> better splitting
  136. # 2.4.2 28.12.2016 Attribut "verbose4Devs" to restrict verbose4 loggings of specific devices
  137. # 2.4.1 27.12.2016 DbLog_Push: improved update/insert into current, analyze execute_array -> ArrayTupleStatus
  138. # 2.4 24.12.2016 some improvements of verbose 4 logging
  139. # 2.3.1 23.12.2016 fix due to https://forum.fhem.de/index.php/topic,62998.msg545541.html#msg545541
  140. # 2.3 22.12.2016 fix eval{} in DbLog_Log
  141. # 2.2 21.12.2016 set DbLogType only to "History" if attr DbLogType not set
  142. # 2.1 21.12.2016 use execute_array in DbLog_Push
  143. # 2.0 19.12.2016 some improvements DbLog_Log
  144. # 1.9.3 17.12.2016 $hash->{NOTIFYDEV} added to process only events from devices are in Regex
  145. # 1.9.2 17.12.2016 some improvemnts DbLog_Log, DbLog_Push
  146. # 1.9.1 16.12.2016 DbLog_Log no using encode_base64
  147. # 1.9 16.12.2016 DbLog_Push changed to use deviceEvents
  148. # 1.8.1 16.12.2016 DbLog_Push changed
  149. # 1.8 15.12.2016 bugfix of don't logging all received events
  150. # 1.7.1 15.12.2016 attr procedure of "disabled" changed
  151. package main;
  152. use strict;
  153. use warnings;
  154. eval "use DBI;1" or my $DbLogMMDBI = "DBI";
  155. use Data::Dumper;
  156. use Blocking;
  157. use Time::HiRes qw(gettimeofday tv_interval);
  158. use Encode qw(encode_utf8);
  159. my $DbLogVersion = "2.22.14";
  160. my %columns = ("DEVICE" => 64,
  161. "TYPE" => 64,
  162. "EVENT" => 512,
  163. "READING" => 64,
  164. "VALUE" => 128,
  165. "UNIT" => 32
  166. );
  167. sub dbReadings($@);
  168. ################################################################
  169. sub DbLog_Initialize($)
  170. {
  171. my ($hash) = @_;
  172. $hash->{DefFn} = "DbLog_Define";
  173. $hash->{UndefFn} = "DbLog_Undef";
  174. $hash->{NotifyFn} = "DbLog_Log";
  175. $hash->{SetFn} = "DbLog_Set";
  176. $hash->{GetFn} = "DbLog_Get";
  177. $hash->{AttrFn} = "DbLog_Attr";
  178. $hash->{SVG_regexpFn} = "DbLog_regexpFn";
  179. $hash->{ShutdownFn} = "DbLog_Shutdown";
  180. $hash->{AttrList} = "addStateEvent:0,1 ".
  181. "colEvent ".
  182. "colReading ".
  183. "colValue ".
  184. "disable:1,0 ".
  185. "DbLogType:Current,History,Current/History,SampleFill/History ".
  186. "shutdownWait ".
  187. "suppressUndef:0,1 ".
  188. "verbose4Devs ".
  189. "excludeDevs ".
  190. "expimpdir ".
  191. "syncInterval ".
  192. "noNotifyDev:1,0 ".
  193. "showproctime:1,0 ".
  194. "suppressAddLogV3:1,0 ".
  195. "asyncMode:1,0 ".
  196. "cacheEvents:2,1,0 ".
  197. "cacheLimit ".
  198. "noSupportPK:1,0 ".
  199. "syncEvents:1,0 ".
  200. "showNotifyTime:1,0 ".
  201. "timeout ".
  202. "valueFn:textField-long ".
  203. "DbLogSelectionMode:Exclude,Include,Exclude/Include ".
  204. $readingFnAttributes;
  205. # Das Attribut DbLogSelectionMode legt fest, wie die Device-Spezifischen Atrribute
  206. # DbLogExclude und DbLogInclude behandelt werden sollen.
  207. # - Exclude: Es wird nur das Device-spezifische Attribut Exclude beruecksichtigt,
  208. # d.h. generell wird alles geloggt, was nicht per DBLogExclude ausgeschlossen wird
  209. # - Include: Es wird nur das Device-spezifische Attribut Include beruecksichtigt,
  210. # d.h. generell wird nichts geloggt, ausßer dem was per DBLogInclude eingeschlossen wird
  211. # - Exclude/Include: Es wird zunaechst Exclude geprueft und bei Ausschluß wird ggf. noch zusaetzlich Include geprueft,
  212. # d.h. generell wird alles geloggt, es sei denn es wird per DBLogExclude ausgeschlossen.
  213. # Wird es von DBLogExclude ausgeschlossen, kann es trotzdem wieder per DBLogInclude
  214. # eingeschlossen werden.
  215. addToAttrList("DbLogInclude");
  216. addToAttrList("DbLogExclude");
  217. $hash->{FW_detailFn} = "DbLog_fhemwebFn";
  218. $hash->{SVG_sampleDataFn} = "DbLog_sampleDataFn";
  219. }
  220. ###############################################################
  221. sub DbLog_Define($@)
  222. {
  223. my ($hash, $def) = @_;
  224. my @a = split("[ \t][ \t]*", $def);
  225. return "Error: Perl module ".$DbLogMMDBI." is missing.
  226. Install it on Debian with: sudo apt-get install libdbi-perl" if($DbLogMMDBI);
  227. return "wrong syntax: define <name> DbLog configuration regexp"
  228. if(int(@a) != 4);
  229. $hash->{CONFIGURATION} = $a[2];
  230. my $regexp = $a[3];
  231. eval { "Hallo" =~ m/^$regexp$/ };
  232. return "Bad regexp: $@" if($@);
  233. $hash->{REGEXP} = $regexp;
  234. $hash->{VERSION} = $DbLogVersion;
  235. $hash->{MODE} = AttrVal($hash->{NAME}, "asyncMode", undef)?"asynchronous":"synchronous"; # Mode setzen Forum:#76213
  236. $hash->{HELPER}{OLDSTATE} = "initialized";
  237. # nur Events dieser Devices an NotifyFn weiterleiten, NOTIFYDEV wird gesetzt wenn möglich
  238. notifyRegexpChanged($hash, $regexp);
  239. #remember PID for plotfork
  240. $hash->{PID} = $$;
  241. # CacheIndex für Events zum asynchronen Schreiben in DB
  242. $hash->{cache}{index} = 0;
  243. # read configuration data
  244. my $ret = DbLog_readCfg($hash);
  245. if ($ret) {
  246. # return on error while reading configuration
  247. Log3($hash->{NAME}, 1, "DbLog $hash->{NAME} - Error while reading $hash->{CONFIGURATION}: '$ret' ");
  248. return;
  249. }
  250. # set used COLUMNS
  251. InternalTimer(gettimeofday()+2, "setinternalcols", $hash, 0);
  252. readingsSingleUpdate($hash, 'state', 'waiting for connection', 1);
  253. DbLog_ConnectPush($hash);
  254. # initial execution of DbLog_execmemcache
  255. DbLog_execmemcache($hash);
  256. return undef;
  257. }
  258. ################################################################
  259. sub DbLog_Undef($$) {
  260. my ($hash, $name) = @_;
  261. my $dbh= $hash->{DBHP};
  262. BlockingKill($hash->{HELPER}{RUNNING_PID}) if($hash->{HELPER}{RUNNING_PID});
  263. BlockingKill($hash->{HELPER}{REDUCELOG_PID}) if($hash->{HELPER}{REDUCELOG_PID});
  264. BlockingKill($hash->{HELPER}{COUNT_PID}) if($hash->{HELPER}{COUNT_PID});
  265. BlockingKill($hash->{HELPER}{DELDAYS_PID}) if($hash->{HELPER}{DELDAYS_PID});
  266. $dbh->disconnect() if(defined($dbh));
  267. RemoveInternalTimer($hash);
  268. return undef;
  269. }
  270. ################################################################
  271. sub DbLog_Shutdown($) {
  272. my ($hash) = @_;
  273. my $name = $hash->{NAME};
  274. DbLog_execmemcache($hash);
  275. my $shutdownWait = AttrVal($name,"shutdownWait",undef);
  276. if(defined($shutdownWait)) {
  277. Log3($name, 2, "DbLog $name waiting for shutdown");
  278. sleep($shutdownWait);
  279. }
  280. return undef;
  281. }
  282. ################################################################
  283. #
  284. # Wird bei jeder Aenderung eines Attributes dieser
  285. # DbLog-Instanz aufgerufen
  286. #
  287. ################################################################
  288. sub DbLog_Attr(@) {
  289. my($cmd,$name,$aName,$aVal) = @_;
  290. # my @a = @_;
  291. my $hash = $defs{$name};
  292. my $do = 0;
  293. if($cmd eq "set") {
  294. if ($aName eq "syncInterval" || $aName eq "cacheLimit" || $aName eq "timeout") {
  295. unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 !";}
  296. }
  297. if( $aName eq 'valueFn' ) {
  298. my %specials= (
  299. "%TIMESTAMP" => $name,
  300. "%DEVICE" => $name,
  301. "%DEVICETYPE" => $name,
  302. "%EVENT" => $name,
  303. "%READING" => $name,
  304. "%VALUE" => $name,
  305. "%UNIT" => $name,
  306. "%IGNORE" => $name,
  307. );
  308. my $err = perlSyntaxCheck($aVal, %specials);
  309. return $err if($err);
  310. }
  311. }
  312. if($aName eq "colEvent" || $aName eq "colReading" || $aName eq "colValue") {
  313. if ($cmd eq "set" && $aVal) {
  314. unless ($aVal =~ /^[0-9]+$/) { return " The Value of $aName is not valid. Use only figures 0-9 !";}
  315. }
  316. InternalTimer(gettimeofday()+0.5, "setinternalcols", $hash, 0);
  317. }
  318. if($aName eq "asyncMode") {
  319. if ($cmd eq "set" && $aVal) {
  320. $hash->{MODE} = "asynchronous";
  321. InternalTimer(gettimeofday()+2, "DbLog_execmemcache", $hash, 0);
  322. } else {
  323. $hash->{MODE} = "synchronous";
  324. delete($defs{$name}{READINGS}{NextSync});
  325. delete($defs{$name}{READINGS}{CacheUsage});
  326. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  327. }
  328. }
  329. if($aName eq "showproctime") {
  330. if ($cmd ne "set" || !$aVal) {
  331. delete($defs{$name}{READINGS}{background_processing_time});
  332. delete($defs{$name}{READINGS}{sql_processing_time});
  333. }
  334. }
  335. if($aName eq "showNotifyTime") {
  336. if ($cmd ne "set" || !$aVal) {
  337. delete($defs{$name}{READINGS}{notify_processing_time});
  338. }
  339. }
  340. if($aName eq "noNotifyDev") {
  341. my $regexp = $hash->{REGEXP};
  342. if ($cmd eq "set" && $aVal) {
  343. delete($hash->{NOTIFYDEV});
  344. } else {
  345. notifyRegexpChanged($hash, $regexp);
  346. }
  347. }
  348. if ($aName eq "disable") {
  349. if($cmd eq "set") {
  350. $do = ($aVal) ? 1 : 0;
  351. }
  352. $do = 0 if($cmd eq "del");
  353. my $val = ($do == 1 ? "disabled" : "active");
  354. # letzter CacheSync vor disablen
  355. DbLog_execmemcache($hash) if($do == 1);
  356. readingsSingleUpdate($hash, "state", $val, 1);
  357. $hash->{HELPER}{OLDSTATE} = $val;
  358. if ($do == 0) {
  359. InternalTimer(gettimeofday()+2, "DbLog_execmemcache", $hash, 0);
  360. }
  361. }
  362. return undef;
  363. }
  364. ################################################################
  365. sub DbLog_Set($@) {
  366. my ($hash, @a) = @_;
  367. my $name = $hash->{NAME};
  368. my $async = AttrVal($name, "asyncMode", undef);
  369. my $usage = "Unknown argument, choose one of reduceLog reduceLogNbl reopen rereadcfg:noArg count:noArg countNbl:noArg
  370. deleteOldDays deleteOldDaysNbl userCommand clearReadings:noArg
  371. eraseReadings:noArg addLog ";
  372. $usage .= "listCache:noArg purgeCache:noArg commitCache:noArg exportCache:nopurge,purgecache " if (AttrVal($name, "asyncMode", undef));
  373. $usage .= "configCheck:noArg " if($hash->{MODEL} =~ /MYSQL|POSTGRESQL/);
  374. my (@logs,$dir);
  375. if (!AttrVal($name,"expimpdir",undef)) {
  376. $dir = $attr{global}{modpath}."/log/";
  377. } else {
  378. $dir = AttrVal($name,"expimpdir",undef);
  379. }
  380. opendir(DIR,$dir);
  381. my $sd = "cache_".$name."_";
  382. while (my $file = readdir(DIR)) {
  383. next unless (-f "$dir/$file");
  384. next unless ($file =~ /^$sd/);
  385. push @logs,$file;
  386. }
  387. closedir(DIR);
  388. my $cj = join(",",reverse(sort @logs)) if (@logs);
  389. if (@logs) {
  390. $usage .= "importCachefile:".$cj." ";
  391. } else {
  392. $usage .= "importCachefile ";
  393. }
  394. return $usage if(int(@a) < 2);
  395. my $dbh = $hash->{DBHP};
  396. my $db = (split(/;|=/, $hash->{dbconn}))[1];
  397. my $ret;
  398. if ($a[1] eq 'reduceLog') {
  399. if (defined($a[3]) && $a[3] !~ /^average$|^average=.+|^EXCLUDE=.+$|^INCLUDE=.+$/i) {
  400. return "ReduceLog syntax error in set command. Please see commandref for help.";
  401. }
  402. if (defined $a[2] && $a[2] =~ /^\d+$/) {
  403. $ret = DbLog_reduceLog($hash,@a);
  404. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  405. } else {
  406. Log3($name, 1, "DbLog $name: reduceLog error, no <days> given.");
  407. $ret = "reduceLog error, no <days> given.";
  408. }
  409. }
  410. elsif ($a[1] eq 'reduceLogNbl') {
  411. if (defined($a[3]) && $a[3] !~ /^average$|^average=.+|^EXCLUDE=.+$|^INCLUDE=.+$/i) {
  412. return "ReduceLogNbl syntax error in set command. Please see commandref for help.";
  413. }
  414. if (defined $a[2] && $a[2] =~ /^\d+$/) {
  415. if ($hash->{HELPER}{REDUCELOG_PID} && $hash->{HELPER}{REDUCELOG_PID}{pid} !~ m/DEAD/) {
  416. $ret = "reduceLogNbl already in progress. Please wait for the current process to finish.";
  417. } else {
  418. delete $hash->{HELPER}{REDUCELOG_PID};
  419. my @b = @a;
  420. shift(@b);
  421. readingsSingleUpdate($hash,"reduceLogState","@b started",1);
  422. $hash->{HELPER}{REDUCELOG} = \@a;
  423. $hash->{HELPER}{REDUCELOG_PID} = BlockingCall("DbLog_reduceLogNbl","$name","DbLog_reduceLogNbl_finished");
  424. return;
  425. }
  426. } else {
  427. Log3($name, 1, "DbLog $name: reduceLogNbl error, no <days> given.");
  428. $ret = "reduceLogNbl error, no <days> given.";
  429. }
  430. }
  431. elsif ($a[1] eq 'clearReadings') {
  432. my @allrds = keys%{$defs{$name}{READINGS}};
  433. foreach my $key(@allrds) {
  434. next if($key =~ m/state/ || $key =~ m/CacheUsage/ || $key =~ m/NextSync/);
  435. readingsSingleUpdate($hash,$key," ",0);
  436. }
  437. }
  438. elsif ($a[1] eq 'eraseReadings') {
  439. my @allrds = keys%{$defs{$name}{READINGS}};
  440. foreach my $key(@allrds) {
  441. delete($defs{$name}{READINGS}{$key}) if($key !~ m/^state$/);
  442. }
  443. }
  444. elsif ($a[1] eq 'addLog') {
  445. unless ($a[2]) { return " The argument of $a[1] is not valid. Use a pair of <devicespec>,reading [value] you want to create a log entry from";}
  446. DbLog_AddLog($hash,$a[2],$a[3]);
  447. }
  448. elsif ($a[1] eq 'reopen') {
  449. if ($dbh) {
  450. $dbh->commit() if(!$dbh->{AutoCommit});
  451. $dbh->disconnect();
  452. }
  453. if (!$a[2]) {
  454. Log3($name, 3, "DbLog $name: Reopen requested.");
  455. DbLog_ConnectPush($hash);
  456. delete $hash->{HELPER}{REOPEN_RUNS};
  457. DbLog_execmemcache($hash) if($async);
  458. $ret = "Reopen executed.";
  459. } else {
  460. unless ($a[2] =~ /^[0-9]+$/) { return " The Value of $a[1]-time is not valid. Use only figures 0-9 !";}
  461. # Statusbit "Kein Schreiben in DB erlauben" wenn reopen mit Zeitangabe
  462. $hash->{HELPER}{REOPEN_RUNS} = $a[2];
  463. # falls ein hängender Prozess vorhanden ist -> löschen
  464. BlockingKill($hash->{HELPER}{RUNNING_PID}) if($hash->{HELPER}{RUNNING_PID});
  465. BlockingKill($hash->{HELPER}{REDUCELOG_PID}) if($hash->{HELPER}{REDUCELOG_PID});
  466. BlockingKill($hash->{HELPER}{COUNT_PID}) if($hash->{HELPER}{COUNT_PID});
  467. BlockingKill($hash->{HELPER}{DELDAYS_PID}) if($hash->{HELPER}{DELDAYS_PID});
  468. delete $hash->{HELPER}{RUNNING_PID};
  469. delete $hash->{HELPER}{COUNT_PID};
  470. delete $hash->{HELPER}{DELDAYS_PID};
  471. delete $hash->{HELPER}{REDUCELOG_PID};
  472. my $ts = (split(" ",FmtDateTime(gettimeofday()+$a[2])))[1];
  473. Log3($name, 2, "DbLog $name: Connection closed until $ts ($a[2] seconds).");
  474. readingsSingleUpdate($hash, "state", "closed until $ts ($a[2] seconds)", 1);
  475. InternalTimer(gettimeofday()+$a[2], "reopen", $hash, 0);
  476. }
  477. }
  478. elsif ($a[1] eq 'rereadcfg') {
  479. Log3($name, 3, "DbLog $name: Rereadcfg requested.");
  480. if ($dbh) {
  481. $dbh->commit() if(!$dbh->{AutoCommit});
  482. $dbh->disconnect();
  483. }
  484. $ret = DbLog_readCfg($hash);
  485. return $ret if $ret;
  486. DbLog_ConnectPush($hash);
  487. $ret = "Rereadcfg executed.";
  488. }
  489. elsif ($a[1] eq 'purgeCache') {
  490. delete $hash->{cache};
  491. readingsSingleUpdate($hash, 'CacheUsage', 0, 1);
  492. }
  493. elsif ($a[1] eq 'commitCache') {
  494. DbLog_execmemcache($hash);
  495. }
  496. elsif ($a[1] eq 'listCache') {
  497. my $cache;
  498. foreach my $key (sort{$a <=>$b}keys%{$hash->{cache}{memcache}}) {
  499. $cache .= $key." => ".$hash->{cache}{memcache}{$key}."\n";
  500. }
  501. return $cache;
  502. }
  503. elsif ($a[1] eq 'configCheck') {
  504. my $check = DbLog_configcheck($hash);
  505. return $check;
  506. }
  507. elsif ($a[1] eq 'exportCache') {
  508. my $cln;
  509. my $crows = 0;
  510. my $outfile;
  511. my $now = strftime('%Y-%m-%d_%H-%M-%S',localtime);
  512. # return wenn "reopen" mit Ablaufzeit gestartet ist oder disabled, nicht im asynch-Mode
  513. return if(IsDisabled($name) || $hash->{HELPER}{REOPEN_RUNS});
  514. return if (!AttrVal($name, "asyncMode", undef));
  515. $outfile = $dir."cache_".$name."_".$now;
  516. if (open(FH, ">$outfile")) {
  517. binmode (FH);
  518. } else {
  519. readingsSingleUpdate($hash, "lastCachefile", $outfile." - Error - ".$!, 1);
  520. return "could not open ".$outfile.": ".$!;
  521. }
  522. foreach my $key (sort(keys%{$hash->{cache}{memcache}})) {
  523. $cln = $hash->{cache}{memcache}{$key}."\n";
  524. print FH $cln ;
  525. $crows++;
  526. }
  527. close(FH);
  528. readingsSingleUpdate($hash, "lastCachefile", $outfile." export successful", 1);
  529. readingsSingleUpdate($hash, "state", $crows." cache rows exported to ".$outfile, 1);
  530. Log3($name, 3, "DbLog $name: $crows cache rows exported to $outfile.");
  531. if ($a[-1] =~ m/^purgecache/i) {
  532. delete $hash->{cache};
  533. readingsSingleUpdate($hash, 'CacheUsage', 0, 1);
  534. Log3($name, 3, "DbLog $name: Cache purged after exporting rows to $outfile.");
  535. }
  536. return;
  537. }
  538. elsif ($a[1] eq 'importCachefile') {
  539. my $cln;
  540. my $crows = 0;
  541. my $infile;
  542. my @row_array;
  543. readingsSingleUpdate($hash, "lastCachefile", "", 0);
  544. # return wenn "reopen" mit Ablaufzeit gestartet ist oder disabled
  545. return if(IsDisabled($name) || $hash->{HELPER}{REOPEN_RUNS});
  546. if (!$a[2]) {
  547. return "Wrong function-call. Use set <name> importCachefile <file> without directory (see attr expimpdir)." ;
  548. } else {
  549. $infile = $dir.$a[2];
  550. }
  551. if (open(FH, "$infile")) {
  552. binmode (FH);
  553. } else {
  554. return "could not open ".$infile.": ".$!;
  555. }
  556. while (<FH>) {
  557. push(@row_array, $_);
  558. $crows++;
  559. }
  560. close(FH);
  561. if(@row_array) {
  562. my $error = DbLog_Push($hash, 1, @row_array);
  563. if($error) {
  564. readingsSingleUpdate($hash, "lastCachefile", $infile." - Error - ".$!, 1);
  565. readingsSingleUpdate($hash, "state", $error, 1);
  566. Log3 $name, 5, "DbLog $name -> DbLog_Push Returncode: $error";
  567. } else {
  568. unless(rename($dir.$a[2], $dir."impdone_".$a[2])) {
  569. Log3($name, 2, "DbLog $name: cachefile $infile couldn't be renamed after import !");
  570. }
  571. readingsSingleUpdate($hash, "lastCachefile", $infile." import successful", 1);
  572. readingsSingleUpdate($hash, "state", $crows." cache rows imported from ".$infile, 1);
  573. Log3($name, 3, "DbLog $name: $crows cache rows imported from $infile.");
  574. }
  575. } else {
  576. readingsSingleUpdate($hash, "state", "no rows in ".$infile, 1);
  577. Log3($name, 3, "DbLog $name: $infile doesn't contain any rows - no imports done.");
  578. }
  579. return;
  580. }
  581. elsif ($a[1] eq 'count') {
  582. $dbh = DbLog_ConnectNewDBH($hash);
  583. if(!$dbh) {
  584. Log3($name, 1, "DbLog $name: DBLog_Set - count - DB connect not possible");
  585. return;
  586. } else {
  587. Log3($name, 4, "DbLog $name: Records count requested.");
  588. my $c = $dbh->selectrow_array('SELECT count(*) FROM history');
  589. readingsSingleUpdate($hash, 'countHistory', $c ,1);
  590. $c = $dbh->selectrow_array('SELECT count(*) FROM current');
  591. readingsSingleUpdate($hash, 'countCurrent', $c ,1);
  592. $dbh->disconnect();
  593. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  594. }
  595. }
  596. elsif ($a[1] eq 'countNbl') {
  597. if ($hash->{HELPER}{COUNT_PID} && $hash->{HELPER}{COUNT_PID}{pid} !~ m/DEAD/){
  598. $ret = "DbLog count already in progress. Please wait for the current process to finish.";
  599. } else {
  600. delete $hash->{HELPER}{COUNT_PID};
  601. $hash->{HELPER}{COUNT_PID} = BlockingCall("DbLog_countNbl","$name","DbLog_countNbl_finished");
  602. return;
  603. }
  604. }
  605. elsif ($a[1] eq 'deleteOldDays') {
  606. Log3 ($name, 3, "DbLog $name -> Deletion of records older than $a[2] days in database $db requested");
  607. my ($c, $cmd);
  608. $dbh = DbLog_ConnectNewDBH($hash);
  609. if(!$dbh) {
  610. Log3($name, 1, "DbLog $name: DBLog_Set - deleteOldDays - DB connect not possible");
  611. return;
  612. } else {
  613. $cmd = "delete from history where TIMESTAMP < ";
  614. if ($hash->{MODEL} eq 'SQLITE') { $cmd .= "datetime('now', '-$a[2] days')"; }
  615. elsif ($hash->{MODEL} eq 'MYSQL') { $cmd .= "DATE_SUB(CURDATE(),INTERVAL $a[2] DAY)"; }
  616. elsif ($hash->{MODEL} eq 'POSTGRESQL') { $cmd .= "NOW() - INTERVAL '$a[2]' DAY"; }
  617. else { $cmd = undef; $ret = 'Unknown database type. Maybe you can try userCommand anyway.'; }
  618. if(defined($cmd)) {
  619. $c = $dbh->do($cmd);
  620. $c = 0 if($c == 0E0);
  621. Log3 ($name, 3, "DbLog $name -> deleteOldDays finished. $c entries of database $db deleted.");
  622. readingsSingleUpdate($hash, 'lastRowsDeleted', $c ,1);
  623. }
  624. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  625. }
  626. }
  627. elsif ($a[1] eq 'deleteOldDaysNbl') {
  628. if (defined $a[2] && $a[2] =~ /^\d+$/) {
  629. if ($hash->{HELPER}{DELDAYS_PID} && $hash->{HELPER}{DELDAYS_PID}{pid} !~ m/DEAD/) {
  630. $ret = "deleteOldDaysNbl already in progress. Please wait for the current process to finish.";
  631. } else {
  632. delete $hash->{HELPER}{DELDAYS_PID};
  633. $hash->{HELPER}{DELDAYS} = $a[2];
  634. Log3 ($name, 3, "DbLog $name -> Deletion of records older than $a[2] days in database $db requested");
  635. $hash->{HELPER}{DELDAYS_PID} = BlockingCall("DbLog_deldaysNbl","$name","DbLog_deldaysNbl_done");
  636. return;
  637. }
  638. } else {
  639. Log3($name, 1, "DbLog $name: deleteOldDaysNbl error, no <days> given.");
  640. $ret = "deleteOldDaysNbl error, no <days> given.";
  641. }
  642. }
  643. elsif ($a[1] eq 'userCommand') {
  644. $dbh = DbLog_ConnectNewDBH($hash);
  645. if(!$dbh) {
  646. Log3($name, 1, "DbLog $name: DBLog_Set - userCommand - DB connect not possible");
  647. return;
  648. } else {
  649. Log3($name, 4, "DbLog $name: userCommand execution requested.");
  650. my ($c, @cmd, $sql);
  651. @cmd = @a;
  652. shift(@cmd); shift(@cmd);
  653. $sql = join(" ",@cmd);
  654. readingsSingleUpdate($hash, 'userCommand', $sql, 1);
  655. $c = $dbh->selectrow_array($sql);
  656. my $res = (defined($c))?$c:"no result";
  657. Log3($name, 4, "DbLog $name: DBLog_Set - userCommand - result: $res");
  658. readingsSingleUpdate($hash, 'userCommandResult', $res ,1);
  659. $dbh->disconnect();
  660. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  661. }
  662. }
  663. else { $ret = $usage; }
  664. return $ret;
  665. }
  666. ###############################################################################################
  667. #
  668. # Exrahieren des Filters aus der ColumnsSpec (gplot-Datei)
  669. #
  670. # Die grundlegend idee ist das jeder svg plot einen filter hat der angibt
  671. # welches device und reading dargestellt wird so das der plot sich neu
  672. # lädt wenn es ein entsprechendes event gibt.
  673. #
  674. # Parameter: Quell-Instanz-Name, und alle FileLog-Parameter, die diese Instanz betreffen.
  675. # Quelle: http://forum.fhem.de/index.php/topic,40176.msg325200.html#msg325200
  676. ###############################################################################################
  677. sub DbLog_regexpFn($$) {
  678. my ($name, $filter) = @_;
  679. my $ret;
  680. my @a = split( ' ', $filter );
  681. for(my $i = 0; $i < int(@a); $i++) {
  682. my @fld = split(":", $a[$i]);
  683. $ret .= '|' if( $ret );
  684. no warnings 'uninitialized'; # Forum:74690, bug unitialized
  685. $ret .= $fld[0] .'.'. $fld[1];
  686. use warnings;
  687. }
  688. return $ret;
  689. }
  690. ################################################################
  691. #
  692. # Parsefunktion, abhaengig vom Devicetyp
  693. #
  694. ################################################################
  695. sub DbLog_ParseEvent($$$)
  696. {
  697. my ($device, $type, $event)= @_;
  698. my @result;
  699. my $reading;
  700. my $value;
  701. my $unit;
  702. # Splitfunktion der Eventquelle aufrufen (ab 2.9.1)
  703. ($reading, $value, $unit) = CallInstanceFn($device, "DbLog_splitFn", $event, $device);
  704. # undef bedeutet, Modul stellt keine DbLog_splitFn bereit
  705. if($reading) {
  706. return ($reading, $value, $unit);
  707. }
  708. # split the event into reading, value and unit
  709. # "day-temp: 22.0 (Celsius)" -> "day-temp", "22.0 (Celsius)"
  710. my @parts = split(/: /,$event);
  711. $reading = shift @parts;
  712. if(@parts == 2) {
  713. $value = $parts[0];
  714. $unit = $parts[1];
  715. } else {
  716. $value = join(": ", @parts);
  717. $unit = "";
  718. }
  719. #default
  720. if(!defined($reading)) { $reading = ""; }
  721. if(!defined($value)) { $value = ""; }
  722. if( $value eq "" ) {
  723. $reading= "state";
  724. $value= $event;
  725. }
  726. #globales Abfangen von
  727. # - temperature
  728. # - humidity
  729. if ($reading =~ m(^temperature)) { $unit= "°C"; } # wenn reading mit temperature beginnt
  730. elsif($reading =~ m(^humidity)) { $unit= "%"; }
  731. # the interpretation of the argument depends on the device type
  732. # EMEM, M232Counter, M232Voltage return plain numbers
  733. if(($type eq "M232Voltage") ||
  734. ($type eq "M232Counter") ||
  735. ($type eq "EMEM")) {
  736. }
  737. #OneWire
  738. elsif(($type eq "OWMULTI")) {
  739. if(int(@parts)>1) {
  740. $reading = "data";
  741. $value = $event;
  742. } else {
  743. @parts = split(/\|/, AttrVal($device, $reading."VUnit", ""));
  744. $unit = $parts[1] if($parts[1]);
  745. if(lc($reading) =~ m/temp/) {
  746. $value=~ s/ \(Celsius\)//;
  747. $value=~ s/([-\.\d]+).*/$1/;
  748. $unit= "°C";
  749. }
  750. elsif(lc($reading) =~ m/(humidity|vwc)/) {
  751. $value=~ s/ \(\%\)//;
  752. $unit= "%";
  753. }
  754. }
  755. }
  756. # Onewire
  757. elsif(($type eq "OWAD") ||
  758. ($type eq "OWSWITCH")) {
  759. if(int(@parts)>1) {
  760. $reading = "data";
  761. $value = $event;
  762. } else {
  763. @parts = split(/\|/, AttrVal($device, $reading."Unit", ""));
  764. $unit = $parts[1] if($parts[1]);
  765. }
  766. }
  767. # FBDECT or ZWAVE
  768. elsif (($type eq "FBDECT") || ($type eq "ZWAVE")) {
  769. if ( $value=~/([\.\d]+)\s([a-z].*)/i ) {
  770. $value = $1;
  771. $unit = $2;
  772. }
  773. }
  774. # MAX
  775. elsif(($type eq "MAX")) {
  776. $unit= "°C" if(lc($reading) =~ m/temp/);
  777. $unit= "%" if(lc($reading) eq "valveposition");
  778. }
  779. # FS20
  780. elsif(($type eq "FS20") || ($type eq "X10")) {
  781. if($reading =~ m/^dim(\d+).*/o) {
  782. $value = $1;
  783. $reading= "dim";
  784. $unit= "%";
  785. }
  786. elsif(!defined($value) || $value eq "") {
  787. $value= $reading;
  788. $reading= "data";
  789. }
  790. }
  791. # FHT
  792. elsif($type eq "FHT") {
  793. if($reading =~ m(-from[12]\ ) || $reading =~ m(-to[12]\ )) {
  794. @parts= split(/ /,$event);
  795. $reading= $parts[0];
  796. $value= $parts[1];
  797. $unit= "";
  798. }
  799. elsif($reading =~ m(-temp)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
  800. elsif($reading =~ m(temp-offset)) { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
  801. elsif($reading =~ m(^actuator[0-9]*)) {
  802. if($value eq "lime-protection") {
  803. $reading= "actuator-lime-protection";
  804. undef $value;
  805. }
  806. elsif($value =~ m(^offset:)) {
  807. $reading= "actuator-offset";
  808. @parts= split(/: /,$value);
  809. $value= $parts[1];
  810. if(defined $value) {
  811. $value=~ s/%//; $value= $value*1.; $unit= "%";
  812. }
  813. }
  814. elsif($value =~ m(^unknown_)) {
  815. @parts= split(/: /,$value);
  816. $reading= "actuator-" . $parts[0];
  817. $value= $parts[1];
  818. if(defined $value) {
  819. $value=~ s/%//; $value= $value*1.; $unit= "%";
  820. }
  821. }
  822. elsif($value =~ m(^synctime)) {
  823. $reading= "actuator-synctime";
  824. undef $value;
  825. }
  826. elsif($value eq "test") {
  827. $reading= "actuator-test";
  828. undef $value;
  829. }
  830. elsif($value eq "pair") {
  831. $reading= "actuator-pair";
  832. undef $value;
  833. }
  834. else {
  835. $value=~ s/%//; $value= $value*1.; $unit= "%";
  836. }
  837. }
  838. }
  839. # KS300
  840. elsif($type eq "KS300") {
  841. if($event =~ m(T:.*)) { $reading= "data"; $value= $event; }
  842. elsif($event =~ m(avg_day)) { $reading= "data"; $value= $event; }
  843. elsif($event =~ m(avg_month)) { $reading= "data"; $value= $event; }
  844. elsif($reading eq "temperature") { $value=~ s/ \(Celsius\)//; $unit= "°C"; }
  845. elsif($reading eq "wind") { $value=~ s/ \(km\/h\)//; $unit= "km/h"; }
  846. elsif($reading eq "rain") { $value=~ s/ \(l\/m2\)//; $unit= "l/m2"; }
  847. elsif($reading eq "rain_raw") { $value=~ s/ \(counter\)//; $unit= ""; }
  848. elsif($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
  849. elsif($reading eq "israining") {
  850. $value=~ s/ \(yes\/no\)//;
  851. $value=~ s/no/0/;
  852. $value=~ s/yes/1/;
  853. }
  854. }
  855. # HMS
  856. elsif($type eq "HMS" ||
  857. $type eq "CUL_WS" ||
  858. $type eq "OWTHERM") {
  859. if($event =~ m(T:.*)) { $reading= "data"; $value= $event; }
  860. elsif($reading eq "temperature") {
  861. $value=~ s/ \(Celsius\)//;
  862. $value=~ s/([-\.\d]+).*/$1/; #OWTHERM
  863. $unit= "°C";
  864. }
  865. elsif($reading eq "humidity") { $value=~ s/ \(\%\)//; $unit= "%"; }
  866. elsif($reading eq "battery") {
  867. $value=~ s/ok/1/;
  868. $value=~ s/replaced/1/;
  869. $value=~ s/empty/0/;
  870. }
  871. }
  872. # CUL_HM
  873. elsif ($type eq "CUL_HM") {
  874. # remove trailing %
  875. $value=~ s/ \%$//;
  876. }
  877. # BS
  878. elsif($type eq "BS") {
  879. if($event =~ m(brightness:.*)) {
  880. @parts= split(/ /,$event);
  881. $reading= "lux";
  882. $value= $parts[4]*1.;
  883. $unit= "lux";
  884. }
  885. }
  886. # RFXTRX Lighting
  887. elsif($type eq "TRX_LIGHT") {
  888. if($reading =~ m/^level (\d+)/) {
  889. $value = $1;
  890. $reading= "level";
  891. }
  892. }
  893. # RFXTRX Sensors
  894. elsif($type eq "TRX_WEATHER") {
  895. if($reading eq "energy_current") { $value=~ s/ W//; }
  896. elsif($reading eq "energy_total") { $value=~ s/ kWh//; }
  897. # elsif($reading eq "temperature") {TODO}
  898. # elsif($reading eq "temperature") {TODO
  899. elsif($reading eq "battery") {
  900. if ($value=~ m/(\d+)\%/) {
  901. $value= $1;
  902. }
  903. else {
  904. $value= ($value eq "ok");
  905. }
  906. }
  907. }
  908. # Weather
  909. elsif($type eq "WEATHER") {
  910. if($event =~ m(^wind_condition)) {
  911. @parts= split(/ /,$event); # extract wind direction from event
  912. if(defined $parts[0]) {
  913. $reading = "wind_direction";
  914. $value= $parts[2];
  915. }
  916. }
  917. elsif($reading eq "wind_chill") { $unit= "°C"; }
  918. elsif($reading eq "wind_direction") { $unit= ""; }
  919. elsif($reading =~ m(^wind)) { $unit= "km/h"; } # wind, wind_speed
  920. elsif($reading =~ m(^temperature)) { $unit= "°C"; } # wenn reading mit temperature beginnt
  921. elsif($reading =~ m(^humidity)) { $unit= "%"; }
  922. elsif($reading =~ m(^pressure)) { $unit= "hPa"; }
  923. elsif($reading =~ m(^pressure_trend)) { $unit= ""; }
  924. }
  925. # FHT8V
  926. elsif($type eq "FHT8V") {
  927. if($reading =~ m(valve)) {
  928. @parts= split(/ /,$event);
  929. $reading= $parts[0];
  930. $value= $parts[1];
  931. $unit= "%";
  932. }
  933. }
  934. # Dummy
  935. elsif($type eq "DUMMY") {
  936. if( $value eq "" ) {
  937. $reading= "data";
  938. $value= $event;
  939. }
  940. $unit= "";
  941. }
  942. @result= ($reading,$value,$unit);
  943. return @result;
  944. }
  945. ##################################################################################################################
  946. #
  947. # Hauptroutine zum Loggen. Wird bei jedem Eventchange
  948. # aufgerufen
  949. #
  950. ##################################################################################################################
  951. # Es werden nur die Events von Geräten verarbeitet die im Hash $hash->{NOTIFYDEV} gelistet sind (wenn definiert).
  952. # Dadurch kann die Menge der Events verringert werden. In sub DbRep_Define angeben.
  953. # Beispiele:
  954. # $hash->{NOTIFYDEV} = "global";
  955. # $hash->{NOTIFYDEV} = "global,Definition_A,Definition_B";
  956. sub DbLog_Log($$) {
  957. # $hash is my entry, $dev_hash is the entry of the changed device
  958. my ($hash, $dev_hash) = @_;
  959. my $name = $hash->{NAME};
  960. my $dev_name = $dev_hash->{NAME};
  961. my $dev_type = uc($dev_hash->{TYPE});
  962. my $async = AttrVal($name, "asyncMode", undef);
  963. my $clim = AttrVal($name, "cacheLimit", 500);
  964. my $ce = AttrVal($name, "cacheEvents", 0);
  965. my $net;
  966. return if(IsDisabled($name) || !$hash->{HELPER}{COLSET} || $init_done != 1);
  967. # Notify-Routine Startzeit
  968. my $nst = [gettimeofday];
  969. my $events = deviceEvents($dev_hash, AttrVal($name, "addStateEvent", 1));
  970. return if(!$events);
  971. my $max = int(@{$events});
  972. my $lcdev = lc($dev_name);
  973. # verbose4 Logs nur für Devices in Attr "verbose4Devs"
  974. my $vb4show = 0;
  975. my @vb4devs = split(",", AttrVal($name, "verbose4Devs", ""));
  976. if (!@vb4devs) {
  977. $vb4show = 1;
  978. } else {
  979. foreach (@vb4devs) {
  980. if($dev_name =~ m/$_/i) {
  981. $vb4show = 1;
  982. last;
  983. }
  984. }
  985. Log3 $name, 4, "DbLog $name -> verbose 4 output of device $dev_name skipped due to attribute \"verbose4Devs\" restrictions" if(!$vb4show);
  986. }
  987. if($vb4show) {
  988. Log3 $name, 4, "DbLog $name -> ################################################################";
  989. Log3 $name, 4, "DbLog $name -> ### start of new Logcycle ###";
  990. Log3 $name, 4, "DbLog $name -> ################################################################";
  991. Log3 $name, 4, "DbLog $name -> amount of events received: $max for device: $dev_name";
  992. }
  993. # Devices ausschließen durch Attribut "excludeDevs" (nur wenn kein $hash->{NOTIFYDEV} oder $hash->{NOTIFYDEV} = .*)
  994. if(!$hash->{NOTIFYDEV} || $hash->{NOTIFYDEV} eq ".*") {
  995. my $exc = AttrVal($name, "excludeDevs", "");
  996. $exc =~ s/\s/,/g;
  997. my @exdvs = devspec2array($exc);
  998. if(@exdvs) {
  999. # Log3 $name, 3, "DbLog $name -> excludeDevs: @exdvs";
  1000. foreach (@exdvs) {
  1001. if(lc($dev_name) eq lc($_)) {
  1002. Log3 $name, 4, "DbLog $name -> Device: $dev_name excluded from database logging due to attribute \"excludeDevs\" restrictions" if($vb4show);
  1003. return;
  1004. }
  1005. }
  1006. }
  1007. }
  1008. my $re = $hash->{REGEXP};
  1009. my @row_array;
  1010. my ($event,$reading,$value,$unit);
  1011. my $ts_0 = TimeNow(); # timestamp in SQL format YYYY-MM-DD hh:mm:ss
  1012. my $now = gettimeofday(); # get timestamp in seconds since epoch
  1013. my $DbLogExclude = AttrVal($dev_name, "DbLogExclude", undef);
  1014. my $DbLogInclude = AttrVal($dev_name, "DbLogInclude",undef);
  1015. my $DbLogSelectionMode = AttrVal($name, "DbLogSelectionMode","Exclude");
  1016. my $value_fn = AttrVal( $name, "valueFn", "" );
  1017. # Funktion aus Attr valueFn validieren
  1018. if( $value_fn =~ m/^\s*(\{.*\})\s*$/s ) {
  1019. $value_fn = $1;
  1020. } else {
  1021. $value_fn = '';
  1022. }
  1023. #one Transaction
  1024. eval {
  1025. for (my $i = 0; $i < $max; $i++) {
  1026. my $event = $events->[$i];
  1027. $event = "" if(!defined($event));
  1028. Log3 $name, 4, "DbLog $name -> check Device: $dev_name , Event: $event" if($vb4show);
  1029. if($dev_name =~ m/^$re$/ || "$dev_name:$event" =~ m/^$re$/ || $DbLogSelectionMode eq 'Include') {
  1030. my $timestamp = $ts_0;
  1031. $timestamp = $dev_hash->{CHANGETIME}[$i] if(defined($dev_hash->{CHANGETIME}[$i]));
  1032. my @r = DbLog_ParseEvent($dev_name, $dev_type, $event);
  1033. $reading = $r[0];
  1034. $value = $r[1];
  1035. $unit = $r[2];
  1036. if(!defined $reading) {$reading = "";}
  1037. if(!defined $value) {$value = "";}
  1038. if(!defined $unit || $unit eq "") {$unit = AttrVal("$dev_name", "unit", "");}
  1039. #Je nach DBLogSelectionMode muss das vorgegebene Ergebnis der Include-, bzw. Exclude-Pruefung
  1040. #entsprechend unterschiedlich vorbelegt sein.
  1041. #keine Readings loggen die in DbLogExclude explizit ausgeschlossen sind
  1042. my $DoIt = 0;
  1043. $DoIt = 1 if($DbLogSelectionMode =~ m/Exclude/ );
  1044. if($DbLogExclude && $DbLogSelectionMode =~ m/Exclude/) {
  1045. # Bsp: "(temperature|humidity):300 battery:3600"
  1046. my @v1 = split(/,/, $DbLogExclude);
  1047. for (my $i=0; $i<int(@v1); $i++) {
  1048. my @v2 = split(/:/, $v1[$i]);
  1049. $DoIt = 0 if(!$v2[1] && $reading =~ m/^$v2[0]$/); #Reading matcht auf Regexp, kein MinIntervall angegeben
  1050. if(($v2[1] && $reading =~ m/^$v2[0]$/) && ($v2[1] =~ m/^(\d+)$/)) {
  1051. #Regexp matcht und MinIntervall ist angegeben
  1052. my $lt = $defs{$dev_hash->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{TIME};
  1053. my $lv = $defs{$dev_hash->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{VALUE};
  1054. $lt = 0 if(!$lt);
  1055. $lv = "" if(!$lv);
  1056. if(($now-$lt < $v2[1]) && ($lv eq $value)) {
  1057. # innerhalb MinIntervall und LastValue=Value
  1058. $DoIt = 0;
  1059. }
  1060. }
  1061. }
  1062. }
  1063. #Hier ggf. zusätzlich noch dbLogInclude pruefen, falls bereits durch DbLogExclude ausgeschlossen
  1064. #Im Endeffekt genau die gleiche Pruefung, wie fuer DBLogExclude, lediglich mit umgegkehrtem Ergebnis.
  1065. if($DoIt == 0) {
  1066. if($DbLogInclude && ($DbLogSelectionMode =~ m/Include/)) {
  1067. my @v1 = split(/,/, $DbLogInclude);
  1068. for (my $i=0; $i<int(@v1); $i++) {
  1069. my @v2 = split(/:/, $v1[$i]);
  1070. $DoIt = 1 if($reading =~ m/^$v2[0]$/); #Reading matcht auf Regexp
  1071. if(($v2[1] && $reading =~ m/^$v2[0]$/) && ($v2[1] =~ m/^(\d+)$/)) {
  1072. #Regexp matcht und MinIntervall ist angegeben
  1073. my $lt = $defs{$dev_hash->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{TIME};
  1074. my $lv = $defs{$dev_hash->{NAME}}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{VALUE};
  1075. $lt = 0 if(!$lt);
  1076. $lv = "" if(!$lv);
  1077. if(($now-$lt < $v2[1]) && ($lv eq $value)) {
  1078. # innerhalb MinIntervall und LastValue=Value
  1079. $DoIt = 0;
  1080. }
  1081. }
  1082. }
  1083. }
  1084. }
  1085. next if($DoIt == 0);
  1086. if ($DoIt) {
  1087. $defs{$dev_name}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{TIME} = $now;
  1088. $defs{$dev_name}{Helper}{DBLOG}{$reading}{$hash->{NAME}}{VALUE} = $value;
  1089. # Anwender kann Feldwerte mit Funktion aus Attr valueFn verändern oder Datensatz-Log überspringen
  1090. if($value_fn ne '') {
  1091. my $TIMESTAMP = $timestamp;
  1092. my $DEVICE = $dev_name;
  1093. my $DEVICETYPE = $dev_type;
  1094. my $EVENT = $event;
  1095. my $READING = $reading;
  1096. my $VALUE = $value;
  1097. my $UNIT = $unit;
  1098. my $IGNORE = 0;
  1099. eval $value_fn;
  1100. Log3 $name, 2, "DbLog $name -> error valueFn: ".$@ if($@);
  1101. if($IGNORE) {
  1102. # aktueller Event wird nicht geloggt wenn $IGNORE=1 gesetzt in $value_fn
  1103. Log3 $hash->{NAME}, 4, "DbLog $name -> Event ignored by valueFn - TS: $timestamp, Device: $dev_name, Type: $dev_type, Event: $event, Reading: $reading, Value: $value, Unit: $unit"
  1104. if($vb4show);
  1105. next;
  1106. }
  1107. $timestamp = $TIMESTAMP if($TIMESTAMP =~ /(19[0-9][0-9]|2[0-9][0-9][0-9])-(0[1-9]|1[1-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]) (0[0-9]|1[1-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])/);
  1108. $dev_name = $DEVICE if($DEVICE ne '');
  1109. $dev_type = $DEVICETYPE if($DEVICETYPE ne '');
  1110. $reading = $READING if($READING ne '');
  1111. $value = $VALUE if(defined $VALUE);
  1112. $unit = $UNIT if(defined $UNIT);
  1113. }
  1114. # Daten auf maximale Länge beschneiden
  1115. ($dev_name,$dev_type,$event,$reading,$value,$unit) = DbLog_cutCol($hash,$dev_name,$dev_type,$event,$reading,$value,$unit);
  1116. my $row = ($timestamp."|".$dev_name."|".$dev_type."|".$event."|".$reading."|".$value."|".$unit);
  1117. Log3 $hash->{NAME}, 4, "DbLog $name -> added event - Timestamp: $timestamp, Device: $dev_name, Type: $dev_type, Event: $event, Reading: $reading, Value: $value, Unit: $unit"
  1118. if($vb4show);
  1119. if($async) {
  1120. # asynchoner non-blocking Mode
  1121. # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
  1122. $hash->{cache}{index}++;
  1123. my $index = $hash->{cache}{index};
  1124. $hash->{cache}{memcache}{$index} = $row;
  1125. my $memcount = $hash->{cache}{memcache}?scalar(keys%{$hash->{cache}{memcache}}):0;
  1126. if($ce == 1) {
  1127. readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
  1128. } else {
  1129. readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0);
  1130. }
  1131. # asynchrone Schreibroutine aufrufen wenn Füllstand des Cache erreicht ist
  1132. if($memcount >= $clim) {
  1133. Log3 $hash->{NAME}, 5, "DbLog $name -> Number of cache entries reached cachelimit $clim - start database sync.";
  1134. DbLog_execmemcache($hash);
  1135. }
  1136. # Notify-Routine Laufzeit ermitteln
  1137. $net = tv_interval($nst);
  1138. } else {
  1139. # synchoner Mode
  1140. push(@row_array, $row);
  1141. }
  1142. }
  1143. }
  1144. }
  1145. };
  1146. if(!$async) {
  1147. if(@row_array) {
  1148. # synchoner Mode
  1149. # return wenn "reopen" mit Ablaufzeit gestartet ist
  1150. return if($hash->{HELPER}{REOPEN_RUNS});
  1151. my $error = DbLog_Push($hash, $vb4show, @row_array);
  1152. Log3 $name, 5, "DbLog $name -> DbLog_Push Returncode: $error" if($vb4show);
  1153. my $state = $error?$error:(IsDisabled($name))?"disabled":"connected";
  1154. my $evt = ($state eq $hash->{HELPER}{OLDSTATE})?0:1;
  1155. readingsSingleUpdate($hash, "state", $state, $evt);
  1156. $hash->{HELPER}{OLDSTATE} = $state;
  1157. # Notify-Routine Laufzeit ermitteln
  1158. $net = tv_interval($nst);
  1159. }
  1160. }
  1161. if($net && AttrVal($name, "showNotifyTime", undef)) {
  1162. readingsSingleUpdate($hash, "notify_processing_time", sprintf("%.4f",$net), 1);
  1163. }
  1164. return;
  1165. }
  1166. #################################################################################################
  1167. #
  1168. # Schreibroutine Einfügen Werte in DB im Synchronmode
  1169. #
  1170. #################################################################################################
  1171. sub DbLog_Push(@) {
  1172. my ($hash, $vb4show, @row_array) = @_;
  1173. my $name = $hash->{NAME};
  1174. my $DbLogType = AttrVal($name, "DbLogType", "History");
  1175. my $supk = AttrVal($name, "noSupportPK", 0);
  1176. my $errorh = 0;
  1177. my $errorc = 0;
  1178. my $error = 0;
  1179. my $doins = 0; # Hilfsvariable, wenn "1" sollen inserts in Tabele current erfolgen (updates schlugen fehl)
  1180. my $dbh;
  1181. my $nh = ($hash->{MODEL} ne 'SQLITE')?1:0;
  1182. # Unterscheidung $dbh um Abbrüche in Plots (SQLite) zu vermeiden und
  1183. # andererseite kein "MySQL-Server has gone away" Fehler
  1184. if ($nh) {
  1185. $dbh = DbLog_ConnectNewDBH($hash);
  1186. return "Can't connect to database." if(!$dbh);
  1187. } else {
  1188. $dbh = $hash->{DBHP};
  1189. eval {
  1190. if ( !$dbh || not $dbh->ping ) {
  1191. # DB Session dead, try to reopen now !
  1192. DbLog_ConnectPush($hash,1);
  1193. }
  1194. };
  1195. if ($@) {
  1196. Log3($name, 1, "DbLog $name: DBLog_Push - DB Session dead! - $@");
  1197. return $@;
  1198. } else {
  1199. $dbh = $hash->{DBHP};
  1200. }
  1201. }
  1202. $dbh->{RaiseError} = 1;
  1203. $dbh->{PrintError} = 0;
  1204. Log3 $name, 4, "DbLog $name -> ################################################################";
  1205. Log3 $name, 4, "DbLog $name -> ### New database processing cycle - synchronous ###";
  1206. Log3 $name, 4, "DbLog $name -> ################################################################";
  1207. Log3 ($name, 4, "DbLog $name -> DbLogType is: $DbLogType");
  1208. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  1209. my ($usepkh,$usepkc,$pkh,$pkc);
  1210. if (!$supk) {
  1211. ($usepkh,$usepkc,$pkh,$pkc) = checkUsePK($hash,$dbh);
  1212. } else {
  1213. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key usage suppressed by attribute noSupportPK";
  1214. }
  1215. my (@timestamp,@device,@type,@event,@reading,@value,@unit);
  1216. my (@timestamp_cur,@device_cur,@type_cur,@event_cur,@reading_cur,@value_cur,@unit_cur);
  1217. my ($sth_ih,$sth_ic,$sth_uc);
  1218. no warnings 'uninitialized';
  1219. my $ceti = $#row_array+1;
  1220. foreach my $row (@row_array) {
  1221. my @a = split("\\|",$row);
  1222. push(@timestamp, "$a[0]");
  1223. push(@device, "$a[1]");
  1224. push(@type, "$a[2]");
  1225. push(@event, "$a[3]");
  1226. push(@reading, "$a[4]");
  1227. push(@value, "$a[5]");
  1228. push(@unit, "$a[6]");
  1229. Log3 $hash->{NAME}, 4, "DbLog $name -> processing event Timestamp: $a[0], Device: $a[1], Type: $a[2], Event: $a[3], Reading: $a[4], Value: $a[5], Unit: $a[6]"
  1230. if($vb4show);
  1231. }
  1232. use warnings;
  1233. if (lc($DbLogType) =~ m(history)) {
  1234. # insert history mit/ohne primary key
  1235. if ($usepkh && $hash->{MODEL} eq 'MYSQL') {
  1236. eval { $sth_ih = $dbh->prepare("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1237. } elsif ($usepkh && $hash->{MODEL} eq 'SQLITE') {
  1238. eval { $sth_ih = $dbh->prepare("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1239. } elsif ($usepkh && $hash->{MODEL} eq 'POSTGRESQL') {
  1240. eval { $sth_ih = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  1241. } else {
  1242. # old behavior
  1243. eval { $sth_ih = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1244. }
  1245. if ($@) {
  1246. return $@;
  1247. }
  1248. $sth_ih->bind_param_array(1, [@timestamp]);
  1249. $sth_ih->bind_param_array(2, [@device]);
  1250. $sth_ih->bind_param_array(3, [@type]);
  1251. $sth_ih->bind_param_array(4, [@event]);
  1252. $sth_ih->bind_param_array(5, [@reading]);
  1253. $sth_ih->bind_param_array(6, [@value]);
  1254. $sth_ih->bind_param_array(7, [@unit]);
  1255. }
  1256. if (lc($DbLogType) =~ m(current) ) {
  1257. # insert current mit/ohne primary key, insert-values für current werden generiert
  1258. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  1259. eval { $sth_ic = $dbh->prepare("INSERT IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1260. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  1261. eval { $sth_ic = $dbh->prepare("INSERT OR IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1262. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  1263. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  1264. } else {
  1265. # old behavior
  1266. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1267. }
  1268. if ($@) {
  1269. return $@;
  1270. }
  1271. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  1272. # update current (mit PK), insert-values für current wird generiert
  1273. $sth_uc = $dbh->prepare("REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  1274. $sth_uc->bind_param_array(1, [@timestamp]);
  1275. $sth_uc->bind_param_array(2, [@device]);
  1276. $sth_uc->bind_param_array(3, [@type]);
  1277. $sth_uc->bind_param_array(4, [@event]);
  1278. $sth_uc->bind_param_array(5, [@reading]);
  1279. $sth_uc->bind_param_array(6, [@value]);
  1280. $sth_uc->bind_param_array(7, [@unit]);
  1281. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  1282. # update current (mit PK), insert-values für current wird generiert
  1283. $sth_uc = $dbh->prepare("INSERT OR REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  1284. $sth_uc->bind_param_array(1, [@timestamp]);
  1285. $sth_uc->bind_param_array(2, [@device]);
  1286. $sth_uc->bind_param_array(3, [@type]);
  1287. $sth_uc->bind_param_array(4, [@event]);
  1288. $sth_uc->bind_param_array(5, [@reading]);
  1289. $sth_uc->bind_param_array(6, [@value]);
  1290. $sth_uc->bind_param_array(7, [@unit]);
  1291. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  1292. # update current (mit PK), insert-values für current wird generiert
  1293. $sth_uc = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  1294. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  1295. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  1296. $sth_uc->bind_param_array(1, [@timestamp]);
  1297. $sth_uc->bind_param_array(2, [@device]);
  1298. $sth_uc->bind_param_array(3, [@type]);
  1299. $sth_uc->bind_param_array(4, [@event]);
  1300. $sth_uc->bind_param_array(5, [@reading]);
  1301. $sth_uc->bind_param_array(6, [@value]);
  1302. $sth_uc->bind_param_array(7, [@unit]);
  1303. } else {
  1304. # for update current (ohne PK), insert-values für current wird generiert
  1305. $sth_uc = $dbh->prepare("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)");
  1306. $sth_uc->bind_param_array(1, [@timestamp]);
  1307. $sth_uc->bind_param_array(2, [@type]);
  1308. $sth_uc->bind_param_array(3, [@event]);
  1309. $sth_uc->bind_param_array(4, [@value]);
  1310. $sth_uc->bind_param_array(5, [@unit]);
  1311. $sth_uc->bind_param_array(6, [@device]);
  1312. $sth_uc->bind_param_array(7, [@reading]);
  1313. }
  1314. }
  1315. my ($tuples, $rows);
  1316. # insert into history-Tabelle
  1317. eval {$dbh->begin_work();}; # issue: begin_work failed: Turning off AutoCommit failed
  1318. if ($@) {
  1319. Log3($name, 2, "DbLog $name -> Error start transaction for history - $@");
  1320. }
  1321. eval {
  1322. if (lc($DbLogType) =~ m(history) ) {
  1323. ($tuples, $rows) = $sth_ih->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1324. if($tuples && $rows == $ceti) {
  1325. Log3 $hash->{NAME}, 4, "DbLog $name -> $rows of $ceti events inserted into table history".($usepkh?" using PK on columns $pkh":"") if($vb4show);
  1326. } else {
  1327. for my $tuple (0..$#row_array) {
  1328. my $status = $tuple_status[$tuple];
  1329. $status = 0 if($status eq "0E0");
  1330. next if($status); # $status ist "1" wenn insert ok
  1331. Log3 $hash->{NAME}, 3, "DbLog $name -> Insert into history rejected".($usepkh?" (possible PK violation) ":" ")."- TS: $timestamp[$tuple], Device: $device[$tuple], Event: $event[$tuple]" if($vb4show);
  1332. }
  1333. }
  1334. }
  1335. };
  1336. if ($@) {
  1337. Log3 $hash->{NAME}, 2, "DbLog $name -> Error table history - $@";
  1338. $errorh = $@;
  1339. eval {$dbh->rollback() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1340. if ($@) {
  1341. Log3($name, 2, "DbLog $name -> Error rollback history - $@");
  1342. }
  1343. } else {
  1344. eval {$dbh->commit() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1345. if ($@) {
  1346. Log3($name, 2, "DbLog $name -> Error commit history - $@");
  1347. }
  1348. }
  1349. # update or insert current
  1350. eval {$dbh->begin_work();}; # issue: begin_work failed: Turning off AutoCommit failed
  1351. if ($@) {
  1352. Log3($name, 2, "DbLog $name -> Error start transaction for history - $@");
  1353. }
  1354. eval {
  1355. if (lc($DbLogType) =~ m(current) ) {
  1356. ($tuples, $rows) = $sth_uc->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1357. if($tuples && $rows == $ceti) {
  1358. Log3 $hash->{NAME}, 4, "DbLog $name -> $rows of $ceti events updated in table current".($usepkc?" using PK on columns $pkc":"");
  1359. } elsif ($tuples && $rows >= $ceti) {
  1360. Log3 $hash->{NAME}, 4, "DbLog $name -> $ceti events inserted or replaced in table current".($usepkc?" using PK on columns $pkc":"");
  1361. } else {
  1362. $doins = 1;
  1363. $ceti = 0;
  1364. for my $tuple (0..$#device) {
  1365. my $status = $tuple_status[$tuple];
  1366. $status = 0 if($status eq "0E0");
  1367. next if($status); # $status ist "1" wenn update ok
  1368. $ceti++;
  1369. Log3 $hash->{NAME}, 4, "DbLog $name -> Failed to update in current, try to insert: TS: $timestamp[$tuple], Device: $device[$tuple], Reading: $reading[$tuple], Status = $status" if($vb4show);
  1370. push(@timestamp_cur, "$timestamp[$tuple]");
  1371. push(@device_cur, "$device[$tuple]");
  1372. push(@type_cur, "$type[$tuple]");
  1373. push(@event_cur, "$event[$tuple]");
  1374. push(@reading_cur, "$reading[$tuple]");
  1375. push(@value_cur, "$value[$tuple]");
  1376. push(@unit_cur, "$unit[$tuple]");
  1377. }
  1378. }
  1379. if ($doins) {
  1380. # events die nicht in Tabelle current updated wurden, werden in current neu eingefügt
  1381. $sth_ic->bind_param_array(1, [@timestamp_cur]);
  1382. $sth_ic->bind_param_array(2, [@device_cur]);
  1383. $sth_ic->bind_param_array(3, [@type_cur]);
  1384. $sth_ic->bind_param_array(4, [@event_cur]);
  1385. $sth_ic->bind_param_array(5, [@reading_cur]);
  1386. $sth_ic->bind_param_array(6, [@value_cur]);
  1387. $sth_ic->bind_param_array(7, [@unit_cur]);
  1388. ($tuples, $rows) = $sth_ic->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1389. if($tuples && $rows == $ceti) {
  1390. Log3 $hash->{NAME}, 4, "DbLog $name -> $rows of $ceti events inserted into table current".($usepkc?" using PK on columns $pkc":"") if($vb4show);
  1391. } else {
  1392. for my $tuple (0..$#device_cur) {
  1393. my $status = $tuple_status[$tuple];
  1394. $status = 0 if($status eq "0E0");
  1395. next if($status); # $status ist "1" wenn insert ok
  1396. Log3 $hash->{NAME}, 3, "DbLog $name -> Insert into current rejected - TS: $timestamp[$tuple], Device: $device_cur[$tuple], Reading: $reading_cur[$tuple], Status = $status" if($vb4show);
  1397. }
  1398. }
  1399. }
  1400. }
  1401. };
  1402. if ($@) {
  1403. Log3 $hash->{NAME}, 2, "DbLog $name -> Error table current - $@";
  1404. $errorc = $@;
  1405. eval {$dbh->rollback() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1406. if ($@) {
  1407. Log3($name, 2, "DbLog $name -> Error rollback current - $@");
  1408. }
  1409. } else {
  1410. eval {$dbh->commit() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1411. if ($@) {
  1412. Log3($name, 2, "DbLog $name -> Error commit current - $@");
  1413. }
  1414. }
  1415. if ($errorh || $errorc) {
  1416. $error = $errorh.", ".$errorc;
  1417. }
  1418. $dbh->{RaiseError} = 0;
  1419. $dbh->{PrintError} = 1;
  1420. $dbh->disconnect if ($nh);
  1421. return Encode::encode_utf8($error);
  1422. }
  1423. #################################################################################################
  1424. #
  1425. # MemCache auswerten und Schreibroutine asynchron und non-blocking aufrufen
  1426. #
  1427. #################################################################################################
  1428. sub DbLog_execmemcache ($) {
  1429. my ($hash) = @_;
  1430. my $name = $hash->{NAME};
  1431. my $syncival = AttrVal($name, "syncInterval", 30);
  1432. my $clim = AttrVal($name, "cacheLimit", 500);
  1433. my $async = AttrVal($name, "asyncMode", undef);
  1434. my $ce = AttrVal($name, "cacheEvents", 0);
  1435. my $timeout = AttrVal($name, "timeout", 86400);
  1436. my $dbconn = $hash->{dbconn};
  1437. my $dbuser = $hash->{dbuser};
  1438. my $dbpassword = $attr{"sec$name"}{secret};
  1439. my $dolog = 1;
  1440. my $error = 0;
  1441. my (@row_array,$memcount,$dbh);
  1442. RemoveInternalTimer($hash, "DbLog_execmemcache");
  1443. if($init_done != 1) {
  1444. InternalTimer(gettimeofday()+5, "DbLog_execmemcache", $hash, 0);
  1445. return;
  1446. }
  1447. # return wenn "reopen" mit Zeitangabe läuft, oder kein asynchromer Mode oder wenn disabled
  1448. if(!$async || IsDisabled($name) || $hash->{HELPER}{REOPEN_RUNS}) {
  1449. return;
  1450. }
  1451. # tote PID's löschen
  1452. if($hash->{HELPER}{RUNNING_PID} && $hash->{HELPER}{RUNNING_PID}{pid} =~ m/DEAD/) {
  1453. delete $hash->{HELPER}{RUNNING_PID};
  1454. }
  1455. if($hash->{HELPER}{REDUCELOG_PID} && $hash->{HELPER}{REDUCELOG_PID}{pid} =~ m/DEAD/) {
  1456. delete $hash->{HELPER}{REDUCELOG_PID};
  1457. }
  1458. if($hash->{HELPER}{DELDAYS_PID} && $hash->{HELPER}{DELDAYS_PID}{pid} =~ m/DEAD/) {
  1459. delete $hash->{HELPER}{DELDAYS_PID};
  1460. }
  1461. # bei SQLite Sperrverwaltung Logging wenn andere schreibende Zugriffe laufen
  1462. if($hash->{MODEL} eq "SQLITE") {
  1463. if($hash->{HELPER}{DELDAYS_PID}) {
  1464. $error = "deleteOldDaysNbl is running - resync at NextSync";
  1465. $dolog = 0;
  1466. }
  1467. if($hash->{HELPER}{REDUCELOG_PID}) {
  1468. $error = "reduceLogNbl is running - resync at NextSync";
  1469. $dolog = 0;
  1470. }
  1471. if($hash->{HELPER}{RUNNING_PID}) {
  1472. $error = "Commit already running - resync at NextSync";
  1473. $dolog = 0;
  1474. }
  1475. }
  1476. $memcount = $hash->{cache}{memcache}?scalar(keys%{$hash->{cache}{memcache}}):0;
  1477. if($ce == 2) {
  1478. readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
  1479. } else {
  1480. readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0);
  1481. }
  1482. if($memcount && $dolog && !$hash->{HELPER}{RUNNING_PID}) {
  1483. Log3 $name, 5, "DbLog $name -> ################################################################";
  1484. Log3 $name, 5, "DbLog $name -> ### New database processing cycle - asynchronous ###";
  1485. Log3 $name, 5, "DbLog $name -> ################################################################";
  1486. Log3 $hash->{NAME}, 5, "DbLog $name -> MemCache contains $memcount entries to process";
  1487. foreach my $key (sort(keys%{$hash->{cache}{memcache}})) {
  1488. Log3 $hash->{NAME}, 5, "DbLog $name -> MemCache contains: $hash->{cache}{memcache}{$key}";
  1489. push(@row_array, delete($hash->{cache}{memcache}{$key}));
  1490. }
  1491. my $rowlist = join('§', @row_array);
  1492. $rowlist = encode_base64($rowlist,"");
  1493. $hash->{HELPER}{RUNNING_PID} = BlockingCall (
  1494. "DbLog_PushAsync",
  1495. "$name|$rowlist",
  1496. "DbLog_PushAsyncDone",
  1497. $timeout,
  1498. "DbLog_PushAsyncAborted",
  1499. $hash );
  1500. Log3 $hash->{NAME}, 5, "DbLog $name -> DbLog_PushAsync called with timeout: $timeout";
  1501. } else {
  1502. if($hash->{HELPER}{RUNNING_PID}) {
  1503. $error = $error?$error:"Commit already running - resync at NextSync";
  1504. }
  1505. }
  1506. # $memcount = scalar(keys%{$hash->{cache}{memcache}});
  1507. my $nextsync = gettimeofday()+$syncival;
  1508. my $nsdt = FmtDateTime($nextsync);
  1509. if(AttrVal($name, "syncEvents", undef)) {
  1510. readingsSingleUpdate($hash, "NextSync", $nsdt. " or if CacheUsage ".$clim." reached", 1);
  1511. } else {
  1512. readingsSingleUpdate($hash, "NextSync", $nsdt. " or if CacheUsage ".$clim." reached", 0);
  1513. }
  1514. my $state = $error?$error:$hash->{HELPER}{OLDSTATE};
  1515. my $evt = ($state eq $hash->{HELPER}{OLDSTATE})?0:1;
  1516. readingsSingleUpdate($hash, "state", $state, $evt);
  1517. $hash->{HELPER}{OLDSTATE} = $state;
  1518. InternalTimer($nextsync, "DbLog_execmemcache", $hash, 0);
  1519. return;
  1520. }
  1521. #################################################################################################
  1522. #
  1523. # Schreibroutine Einfügen Werte in DB asynchron non-blocking
  1524. #
  1525. #################################################################################################
  1526. sub DbLog_PushAsync(@) {
  1527. my ($string) = @_;
  1528. my ($name,$rowlist) = split("\\|", $string);
  1529. my $hash = $defs{$name};
  1530. my $dbconn = $hash->{dbconn};
  1531. my $dbuser = $hash->{dbuser};
  1532. my $dbpassword = $attr{"sec$name"}{secret};
  1533. my $DbLogType = AttrVal($name, "DbLogType", "History");
  1534. my $supk = AttrVal($name, "noSupportPK", 0);
  1535. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  1536. my $errorh = 0;
  1537. my $errorc = 0;
  1538. my $error = 0;
  1539. my $doins = 0; # Hilfsvariable, wenn "1" sollen inserts in Tabelle current erfolgen (updates schlugen fehl)
  1540. my $dbh;
  1541. my $rowlback = 0; # Eventliste für Rückgabe wenn Fehler
  1542. Log3 ($name, 5, "DbLog $name -> Start DbLog_PushAsync");
  1543. Log3 ($name, 5, "DbLog $name -> DbLogType is: $DbLogType");
  1544. # Background-Startzeit
  1545. my $bst = [gettimeofday];
  1546. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1, mysql_enable_utf8 => $utf8 });};
  1547. if ($@) {
  1548. $error = encode_base64($@,"");
  1549. Log3 ($name, 2, "DbLog $name - Error: $@");
  1550. Log3 ($name, 5, "DbLog $name -> DbLog_PushAsync finished");
  1551. return "$name|$error|0|$rowlist";
  1552. }
  1553. # check ob PK verwendet wird, @usepkx?Anzahl der Felder im PK:0 wenn kein PK, $pkx?Namen der Felder:none wenn kein PK
  1554. my ($usepkh,$usepkc,$pkh,$pkc);
  1555. if (!$supk) {
  1556. ($usepkh,$usepkc,$pkh,$pkc) = checkUsePK($hash,$dbh);
  1557. } else {
  1558. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key usage suppressed by attribute noSupportPK";
  1559. }
  1560. my $rowldec = decode_base64($rowlist);
  1561. my @row_array = split('§', $rowldec);
  1562. my (@timestamp,@device,@type,@event,@reading,@value,@unit);
  1563. my (@timestamp_cur,@device_cur,@type_cur,@event_cur,@reading_cur,@value_cur,@unit_cur);
  1564. my ($sth_ih,$sth_ic,$sth_uc);
  1565. no warnings 'uninitialized';
  1566. my $ceti = $#row_array+1;
  1567. foreach my $row (@row_array) {
  1568. my @a = split("\\|",$row);
  1569. push(@timestamp, "$a[0]");
  1570. push(@device, "$a[1]");
  1571. push(@type, "$a[2]");
  1572. push(@event, "$a[3]");
  1573. push(@reading, "$a[4]");
  1574. push(@value, "$a[5]");
  1575. push(@unit, "$a[6]");
  1576. Log3 $hash->{NAME}, 5, "DbLog $name -> processing event Timestamp: $a[0], Device: $a[1], Type: $a[2], Event: $a[3], Reading: $a[4], Value: $a[5], Unit: $a[6]";
  1577. }
  1578. use warnings;
  1579. if (lc($DbLogType) =~ m(history)) {
  1580. # insert history mit/ohne primary key
  1581. if ($usepkh && $hash->{MODEL} eq 'MYSQL') {
  1582. eval { $sth_ih = $dbh->prepare("INSERT IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1583. } elsif ($usepkh && $hash->{MODEL} eq 'SQLITE') {
  1584. eval { $sth_ih = $dbh->prepare("INSERT OR IGNORE INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1585. } elsif ($usepkh && $hash->{MODEL} eq 'POSTGRESQL') {
  1586. eval { $sth_ih = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  1587. } else {
  1588. # old behavior
  1589. eval { $sth_ih = $dbh->prepare("INSERT INTO history (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1590. }
  1591. if ($@) {
  1592. # Eventliste zurückgeben wenn z.B. disk I/O error bei SQLITE
  1593. $error = encode_base64($@,"");
  1594. Log3 ($name, 2, "DbLog $name - Error: $@");
  1595. Log3 ($name, 5, "DbLog $name -> DbLog_PushAsync finished");
  1596. $dbh->disconnect();
  1597. return "$name|$error|0|$rowlist";
  1598. }
  1599. $sth_ih->bind_param_array(1, [@timestamp]);
  1600. $sth_ih->bind_param_array(2, [@device]);
  1601. $sth_ih->bind_param_array(3, [@type]);
  1602. $sth_ih->bind_param_array(4, [@event]);
  1603. $sth_ih->bind_param_array(5, [@reading]);
  1604. $sth_ih->bind_param_array(6, [@value]);
  1605. $sth_ih->bind_param_array(7, [@unit]);
  1606. }
  1607. if (lc($DbLogType) =~ m(current) ) {
  1608. # insert current mit/ohne primary key, insert-values für current werden generiert
  1609. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  1610. eval { $sth_ic = $dbh->prepare("INSERT IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1611. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  1612. eval { $sth_ic = $dbh->prepare("INSERT OR IGNORE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1613. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  1614. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT DO NOTHING"); };
  1615. } else {
  1616. # old behavior
  1617. eval { $sth_ic = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)"); };
  1618. }
  1619. if ($@) {
  1620. # Eventliste zurückgeben wenn z.B. Disk I/O error bei SQLITE
  1621. $error = encode_base64($@,"");
  1622. Log3 ($name, 2, "DbLog $name - Error: $@");
  1623. Log3 ($name, 5, "DbLog $name -> DbLog_PushAsync finished");
  1624. $dbh->disconnect();
  1625. return "$name|$error|0|$rowlist";
  1626. }
  1627. if ($usepkc && $hash->{MODEL} eq 'MYSQL') {
  1628. # update current (mit PK), insert-values für current wird generiert
  1629. $sth_uc = $dbh->prepare("REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  1630. $sth_uc->bind_param_array(1, [@timestamp]);
  1631. $sth_uc->bind_param_array(2, [@device]);
  1632. $sth_uc->bind_param_array(3, [@type]);
  1633. $sth_uc->bind_param_array(4, [@event]);
  1634. $sth_uc->bind_param_array(5, [@reading]);
  1635. $sth_uc->bind_param_array(6, [@value]);
  1636. $sth_uc->bind_param_array(7, [@unit]);
  1637. } elsif ($usepkc && $hash->{MODEL} eq 'SQLITE') {
  1638. # update current (mit PK), insert-values für current wird generiert
  1639. $sth_uc = $dbh->prepare("INSERT OR REPLACE INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?)");
  1640. $sth_uc->bind_param_array(1, [@timestamp]);
  1641. $sth_uc->bind_param_array(2, [@device]);
  1642. $sth_uc->bind_param_array(3, [@type]);
  1643. $sth_uc->bind_param_array(4, [@event]);
  1644. $sth_uc->bind_param_array(5, [@reading]);
  1645. $sth_uc->bind_param_array(6, [@value]);
  1646. $sth_uc->bind_param_array(7, [@unit]);
  1647. } elsif ($usepkc && $hash->{MODEL} eq 'POSTGRESQL') {
  1648. # update current (mit PK), insert-values für current wird generiert
  1649. $sth_uc = $dbh->prepare("INSERT INTO current (TIMESTAMP, DEVICE, TYPE, EVENT, READING, VALUE, UNIT) VALUES (?,?,?,?,?,?,?) ON CONFLICT ($pkc)
  1650. DO UPDATE SET TIMESTAMP=EXCLUDED.TIMESTAMP, DEVICE=EXCLUDED.DEVICE, TYPE=EXCLUDED.TYPE, EVENT=EXCLUDED.EVENT, READING=EXCLUDED.READING,
  1651. VALUE=EXCLUDED.VALUE, UNIT=EXCLUDED.UNIT");
  1652. $sth_uc->bind_param_array(1, [@timestamp]);
  1653. $sth_uc->bind_param_array(2, [@device]);
  1654. $sth_uc->bind_param_array(3, [@type]);
  1655. $sth_uc->bind_param_array(4, [@event]);
  1656. $sth_uc->bind_param_array(5, [@reading]);
  1657. $sth_uc->bind_param_array(6, [@value]);
  1658. $sth_uc->bind_param_array(7, [@unit]);
  1659. } else {
  1660. # update current (ohne PK), insert-values für current wird generiert
  1661. $sth_uc = $dbh->prepare("UPDATE current SET TIMESTAMP=?, TYPE=?, EVENT=?, VALUE=?, UNIT=? WHERE (DEVICE=?) AND (READING=?)");
  1662. $sth_uc->bind_param_array(1, [@timestamp]);
  1663. $sth_uc->bind_param_array(2, [@type]);
  1664. $sth_uc->bind_param_array(3, [@event]);
  1665. $sth_uc->bind_param_array(4, [@value]);
  1666. $sth_uc->bind_param_array(5, [@unit]);
  1667. $sth_uc->bind_param_array(6, [@device]);
  1668. $sth_uc->bind_param_array(7, [@reading]);
  1669. }
  1670. }
  1671. # SQL-Startzeit
  1672. my $st = [gettimeofday];
  1673. my ($tuples, $rows);
  1674. # insert into history
  1675. eval {$dbh->begin_work();}; # issue: begin_work failed: Turning off AutoCommit failed
  1676. if ($@) {
  1677. Log3($name, 2, "DbLog $name -> Error start transaction for history - $@");
  1678. }
  1679. eval {
  1680. if (lc($DbLogType) =~ m(history) ) {
  1681. ($tuples, $rows) = $sth_ih->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1682. if($tuples && $rows == $ceti) {
  1683. Log3 $hash->{NAME}, 5, "DbLog $name -> $rows of $ceti events inserted into table history".($usepkh?" using PK on columns $pkh":"");
  1684. } else {
  1685. for my $tuple (0..$#row_array) {
  1686. my $status = $tuple_status[$tuple];
  1687. $status = 0 if($status eq "0E0");
  1688. next if($status); # $status ist "1" wenn insert ok
  1689. Log3 $hash->{NAME}, 3, "DbLog $name -> Insert into history rejected".($usepkh?" (possible PK violation) ":" ")."- TS: $timestamp[$tuple], Device: $device[$tuple], Event: $event[$tuple]";
  1690. }
  1691. }
  1692. }
  1693. };
  1694. if ($@) {
  1695. Log3 $hash->{NAME}, 2, "DbLog $name -> Error table history - $@";
  1696. $errorh = $@;
  1697. eval {$dbh->rollback() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1698. if ($@) {
  1699. Log3($name, 2, "DbLog $name -> Error rollback history - $@");
  1700. }
  1701. $rowlback = $rowlist;
  1702. } else {
  1703. eval {$dbh->commit() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1704. if ($@) {
  1705. Log3($name, 2, "DbLog $name -> Error commit history - $@");
  1706. }
  1707. }
  1708. # update or insert current
  1709. eval {$dbh->begin_work();}; # issue: begin_work failed: Turning off AutoCommit failed
  1710. if ($@) {
  1711. Log3($name, 2, "DbLog $name -> Error start transaction for current - $@");
  1712. }
  1713. eval {
  1714. if (lc($DbLogType) =~ m(current) ) {
  1715. ($tuples, $rows) = $sth_uc->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1716. if($tuples && $rows == $ceti) {
  1717. Log3 $hash->{NAME}, 5, "DbLog $name -> $rows of $ceti events updated in table current".($usepkc?" using PK on columns $pkc":"");
  1718. } elsif ($tuples && $rows >= $ceti) {
  1719. Log3 $hash->{NAME}, 5, "DbLog $name -> $ceti events inserted or replaced in table current".($usepkc?" using PK on columns $pkc":"");
  1720. } else {
  1721. $doins = 1;
  1722. $ceti = 0;
  1723. for my $tuple (0..$#device) {
  1724. my $status = $tuple_status[$tuple];
  1725. $status = 0 if($status eq "0E0");
  1726. next if($status); # $status ist "1" wenn update ok
  1727. $ceti++;
  1728. Log3 $hash->{NAME}, 5, "DbLog $name -> Failed to update in current, try to insert - TS: $timestamp[$tuple], Device: $device[$tuple], Reading: $reading[$tuple], Status = $status";
  1729. push(@timestamp_cur, "$timestamp[$tuple]");
  1730. push(@device_cur, "$device[$tuple]");
  1731. push(@type_cur, "$type[$tuple]");
  1732. push(@event_cur, "$event[$tuple]");
  1733. push(@reading_cur, "$reading[$tuple]");
  1734. push(@value_cur, "$value[$tuple]");
  1735. push(@unit_cur, "$unit[$tuple]");
  1736. }
  1737. }
  1738. if ($doins) {
  1739. # events die nicht in Tabelle current updated wurden, werden in current neu eingefügt
  1740. $sth_ic->bind_param_array(1, [@timestamp_cur]);
  1741. $sth_ic->bind_param_array(2, [@device_cur]);
  1742. $sth_ic->bind_param_array(3, [@type_cur]);
  1743. $sth_ic->bind_param_array(4, [@event_cur]);
  1744. $sth_ic->bind_param_array(5, [@reading_cur]);
  1745. $sth_ic->bind_param_array(6, [@value_cur]);
  1746. $sth_ic->bind_param_array(7, [@unit_cur]);
  1747. ($tuples, $rows) = $sth_ic->execute_array( { ArrayTupleStatus => \my @tuple_status } );
  1748. if($tuples && $rows == $ceti) {
  1749. Log3 $hash->{NAME}, 5, "DbLog $name -> $rows of $ceti events inserted into table current".($usepkc?" using PK on columns $pkc":"");
  1750. } else {
  1751. for my $tuple (0..$#device_cur) {
  1752. my $status = $tuple_status[$tuple];
  1753. $status = 0 if($status eq "0E0");
  1754. next if($status); # $status ist "1" wenn insert ok
  1755. Log3 $hash->{NAME}, 2, "DbLog $name -> Insert into current rejected - TS: $timestamp[$tuple], Device: $device_cur[$tuple], Reading: $reading_cur[$tuple], Status = $status";
  1756. }
  1757. }
  1758. }
  1759. }
  1760. };
  1761. if ($@) {
  1762. Log3 $hash->{NAME}, 2, "DbLog $name -> Error table current - $@";
  1763. $errorc = $@;
  1764. eval {$dbh->rollback() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1765. if ($@) {
  1766. Log3($name, 2, "DbLog $name -> Error rollback current - $@");
  1767. }
  1768. } else {
  1769. eval {$dbh->commit() if(!$dbh->{AutoCommit});}; # issue Turning on AutoCommit failed
  1770. if ($@) {
  1771. Log3($name, 2, "DbLog $name -> Error commit current - $@");
  1772. }
  1773. }
  1774. $dbh->disconnect();
  1775. # SQL-Laufzeit ermitteln
  1776. my $rt = tv_interval($st);
  1777. if ($errorh || $errorc) {
  1778. my $err = $errorh.", ".$errorc;
  1779. $error = encode_base64($err,"");
  1780. }
  1781. Log3 ($name, 5, "DbLog $name -> DbLog_PushAsync finished");
  1782. # Background-Laufzeit ermitteln
  1783. my $brt = tv_interval($bst);
  1784. $rt = $rt.",".$brt;
  1785. return "$name|$error|$rt|$rowlback";
  1786. }
  1787. #############################################################################################
  1788. # Auswertung non-blocking asynchron DbLog_PushAsync
  1789. #############################################################################################
  1790. sub DbLog_PushAsyncDone ($) {
  1791. my ($string) = @_;
  1792. my @a = split("\\|",$string);
  1793. my $name = $a[0];
  1794. my $hash = $defs{$name};
  1795. my $error = $a[1]?decode_base64($a[1]):0;
  1796. my $bt = $a[2];
  1797. my $rowlist = $a[3];
  1798. my $asyncmode = AttrVal($name, "asyncMode", undef);
  1799. my $memcount;
  1800. Log3 ($name, 5, "DbLog $name -> Start DbLog_PushAsyncDone");
  1801. if($rowlist) {
  1802. $rowlist = decode_base64($rowlist);
  1803. my @row_array = split('§', $rowlist);
  1804. #one Transaction
  1805. eval {
  1806. foreach my $row (@row_array) {
  1807. # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
  1808. $hash->{cache}{index}++;
  1809. my $index = $hash->{cache}{index};
  1810. $hash->{cache}{memcache}{$index} = $row;
  1811. }
  1812. $memcount = scalar(keys%{$hash->{cache}{memcache}});
  1813. };
  1814. }
  1815. # $state = "disabled" if(IsDisabled($name));
  1816. $memcount = $hash->{cache}{memcache}?scalar(keys%{$hash->{cache}{memcache}}):0;
  1817. readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0);
  1818. if(AttrVal($name, "showproctime", undef) && $bt) {
  1819. my ($rt,$brt) = split(",", $bt);
  1820. readingsBeginUpdate($hash);
  1821. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt));
  1822. readingsBulkUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt));
  1823. readingsEndUpdate($hash, 1);
  1824. }
  1825. my $state = $error?$error:(IsDisabled($name))?"disabled":"connected";
  1826. my $evt = ($state eq $hash->{HELPER}{OLDSTATE})?0:1;
  1827. readingsSingleUpdate($hash, "state", $state, $evt);
  1828. $hash->{HELPER}{OLDSTATE} = $state;
  1829. if(!$asyncmode) {
  1830. delete($defs{$name}{READINGS}{NextSync});
  1831. delete($defs{$name}{READINGS}{background_processing_time});
  1832. delete($defs{$name}{READINGS}{sql_processing_time});
  1833. delete($defs{$name}{READINGS}{CacheUsage});
  1834. }
  1835. delete $hash->{HELPER}{RUNNING_PID};
  1836. Log3 ($name, 5, "DbLog $name -> DbLog_PushAsyncDone finished");
  1837. return;
  1838. }
  1839. #############################################################################################
  1840. # Abbruchroutine Timeout non-blocking asynchron DbLog_PushAsync
  1841. #############################################################################################
  1842. sub DbLog_PushAsyncAborted(@) {
  1843. my ($hash,$cause) = @_;
  1844. my $name = $hash->{NAME};
  1845. $cause = $cause?$cause:"Timeout: process terminated";
  1846. Log3 ($name, 2, "DbLog $name -> $hash->{HELPER}{RUNNING_PID}{fn} $cause");
  1847. readingsSingleUpdate($hash,"state",$cause, 1);
  1848. delete $hash->{HELPER}{RUNNING_PID};
  1849. }
  1850. ################################################################
  1851. #
  1852. # zerlegt uebergebenes FHEM-Datum in die einzelnen Bestandteile
  1853. # und fuegt noch Defaultwerte ein
  1854. # uebergebenes SQL-Format: YYYY-MM-DD HH24:MI:SS
  1855. #
  1856. ################################################################
  1857. sub DbLog_explode_datetime($%) {
  1858. my ($t, %def) = @_;
  1859. my %retv;
  1860. my (@datetime, @date, @time);
  1861. @datetime = split(" ", $t); #Datum und Zeit auftrennen
  1862. @date = split("-", $datetime[0]);
  1863. @time = split(":", $datetime[1]) if ($datetime[1]);
  1864. if ($date[0]) {$retv{year} = $date[0];} else {$retv{year} = $def{year};}
  1865. if ($date[1]) {$retv{month} = $date[1];} else {$retv{month} = $def{month};}
  1866. if ($date[2]) {$retv{day} = $date[2];} else {$retv{day} = $def{day};}
  1867. if ($time[0]) {$retv{hour} = $time[0];} else {$retv{hour} = $def{hour};}
  1868. if ($time[1]) {$retv{minute}= $time[1];} else {$retv{minute}= $def{minute};}
  1869. if ($time[2]) {$retv{second}= $time[2];} else {$retv{second}= $def{second};}
  1870. $retv{datetime}=DbLog_implode_datetime($retv{year}, $retv{month}, $retv{day}, $retv{hour}, $retv{minute}, $retv{second});
  1871. #Log 1, Dumper(%retv);
  1872. return %retv
  1873. }
  1874. sub DbLog_implode_datetime($$$$$$) {
  1875. my ($year, $month, $day, $hour, $minute, $second) = @_;
  1876. my $retv = $year."-".$month."-".$day." ".$hour.":".$minute.":".$second;
  1877. return $retv;
  1878. }
  1879. ###################################################################################
  1880. # Verbindungen zur DB aufbauen
  1881. ###################################################################################
  1882. sub DbLog_readCfg($){
  1883. my ($hash)= @_;
  1884. my $name = $hash->{NAME};
  1885. my $configfilename= $hash->{CONFIGURATION};
  1886. my %dbconfig;
  1887. # use generic fileRead to get configuration data
  1888. my ($err, @config) = FileRead($configfilename);
  1889. return $err if($err);
  1890. eval join("\n", @config);
  1891. return "could not read connection" if (!defined $dbconfig{connection});
  1892. $hash->{dbconn} = $dbconfig{connection};
  1893. return "could not read user" if (!defined $dbconfig{user});
  1894. $hash->{dbuser} = $dbconfig{user};
  1895. return "could not read password" if (!defined $dbconfig{password});
  1896. $attr{"sec$name"}{secret} = $dbconfig{password};
  1897. #check the database model
  1898. if($hash->{dbconn} =~ m/pg:/i) {
  1899. $hash->{MODEL}="POSTGRESQL";
  1900. } elsif ($hash->{dbconn} =~ m/mysql:/i) {
  1901. $hash->{MODEL}="MYSQL";
  1902. } elsif ($hash->{dbconn} =~ m/oracle:/i) {
  1903. $hash->{MODEL}="ORACLE";
  1904. } elsif ($hash->{dbconn} =~ m/sqlite:/i) {
  1905. $hash->{MODEL}="SQLITE";
  1906. } else {
  1907. $hash->{MODEL}="unknown";
  1908. Log3 $hash->{NAME}, 1, "Unknown database model found in configuration file $configfilename.";
  1909. Log3 $hash->{NAME}, 1, "Only MySQL/MariaDB, PostgreSQL, Oracle, SQLite are fully supported.";
  1910. return "unknown database type";
  1911. }
  1912. if($hash->{MODEL} eq "MYSQL") {
  1913. $hash->{UTF8} = defined($dbconfig{utf8})?$dbconfig{utf8}:0;
  1914. }
  1915. return;
  1916. }
  1917. sub DbLog_ConnectPush($;$$) {
  1918. # own $dbhp for synchronous logging and dblog_get
  1919. my ($hash,$get)= @_;
  1920. my $name = $hash->{NAME};
  1921. my $dbconn = $hash->{dbconn};
  1922. my $dbuser = $hash->{dbuser};
  1923. my $dbpassword = $attr{"sec$name"}{secret};
  1924. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  1925. my $dbhp;
  1926. Log3 $hash->{NAME}, 3, "DbLog $name: Creating Push-Handle to database $dbconn with user $dbuser" if(!$get);
  1927. eval {$dbhp = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, mysql_enable_utf8 => $utf8 }); };
  1928. if($@) {
  1929. readingsSingleUpdate($hash, 'state', $@, 1);
  1930. Log3 $hash->{NAME}, 3, "DbLog $name: Error - $@";
  1931. }
  1932. if(!$dbhp) {
  1933. RemoveInternalTimer($hash, "DbLog_ConnectPush");
  1934. Log3 $hash->{NAME}, 4, 'DbLog $name: Trying to connect to database';
  1935. readingsSingleUpdate($hash, 'state', 'disconnected', 1);
  1936. InternalTimer(time+5, 'DbLog_ConnectPush', $hash, 0);
  1937. Log3 $hash->{NAME}, 4, 'Waiting for database connection';
  1938. return 0;
  1939. }
  1940. Log3 $hash->{NAME}, 3, "DbLog $name: Push-Handle to db $dbconn created" if(!$get);
  1941. Log3 $hash->{NAME}, 3, "DbLog $name: UTF8 support enabled" if($utf8 && $hash->{MODEL} eq "MYSQL" && !$get);
  1942. readingsSingleUpdate($hash, 'state', 'connected', 1) if(!$get);
  1943. $hash->{DBHP}= $dbhp;
  1944. if ($hash->{MODEL} eq "SQLITE") {
  1945. $dbhp->do("PRAGMA temp_store=MEMORY");
  1946. $dbhp->do("PRAGMA synchronous=NORMAL");
  1947. $dbhp->do("PRAGMA journal_mode=WAL");
  1948. $dbhp->do("PRAGMA cache_size=4000");
  1949. }
  1950. return 1;
  1951. }
  1952. sub DbLog_ConnectNewDBH($) {
  1953. # new dbh for common use (except DbLog_Push and get-function)
  1954. my ($hash)= @_;
  1955. my $name = $hash->{NAME};
  1956. my $dbconn = $hash->{dbconn};
  1957. my $dbuser = $hash->{dbuser};
  1958. my $dbpassword = $attr{"sec$name"}{secret};
  1959. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  1960. my $dbh;
  1961. eval { $dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, mysql_enable_utf8 => $utf8 }); };
  1962. if($@) {
  1963. Log3($name, 2, "DbLog $name: - $@");
  1964. }
  1965. if($dbh) {
  1966. return $dbh;
  1967. } else {
  1968. return 0;
  1969. }
  1970. }
  1971. ##########################################################################
  1972. #
  1973. # Prozedur zum Ausfuehren von SQL-Statements durch externe Module
  1974. #
  1975. # param1: DbLog-hash
  1976. # param2: SQL-Statement
  1977. ##########################################################################
  1978. sub DbLog_ExecSQL($$)
  1979. {
  1980. my ($hash,$sql)= @_;
  1981. Log3 $hash->{NAME}, 4, "Executing $sql";
  1982. my $dbh = DbLog_ConnectNewDBH($hash);
  1983. return if(!$dbh);
  1984. my $sth = DbLog_ExecSQL1($hash,$dbh,$sql);
  1985. if(!$sth) {
  1986. #retry
  1987. $dbh->disconnect();
  1988. $dbh = DbLog_ConnectNewDBH($hash);
  1989. return if(!$dbh);
  1990. $sth = DbLog_ExecSQL1($hash,$dbh,$sql);
  1991. if(!$sth) {
  1992. Log3 $hash->{NAME}, 2, "DBLog retry failed.";
  1993. return 0;
  1994. }
  1995. Log3 $hash->{NAME}, 2, "DBLog retry ok.";
  1996. }
  1997. return $sth;
  1998. }
  1999. sub DbLog_ExecSQL1($$$)
  2000. {
  2001. my ($hash,$dbh,$sql)= @_;
  2002. my $sth = $dbh->do($sql);
  2003. if(!$sth) {
  2004. Log3 $hash->{NAME}, 2, "DBLog error: " . $DBI::errstr;
  2005. return 0;
  2006. }
  2007. return $sth;
  2008. }
  2009. ################################################################
  2010. #
  2011. # GET Funktion
  2012. # wird zb. zur Generierung der Plots implizit aufgerufen
  2013. # infile : [-|current|history]
  2014. # outfile: [-|ALL|INT|WEBCHART]
  2015. #
  2016. ################################################################
  2017. sub DbLog_Get($@) {
  2018. my ($hash, @a) = @_;
  2019. my $name = $hash->{NAME};
  2020. my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0;
  2021. my $dbh;
  2022. return dbReadings($hash,@a) if $a[1] =~ m/^Readings/;
  2023. return "Usage: get $a[0] <in> <out> <from> <to> <column_spec>...\n".
  2024. " where column_spec is <device>:<reading>:<default>:<fn>\n" .
  2025. " see the #DbLog entries in the .gplot files\n" .
  2026. " <in> is not used, only for compatibility for FileLog, please use - \n" .
  2027. " <out> is a prefix, - means stdout\n"
  2028. if(int(@a) < 5);
  2029. shift @a;
  2030. my $inf = lc(shift @a);
  2031. my $outf = lc(shift @a);
  2032. my $from = shift @a;
  2033. my $to = shift @a; # Now @a contains the list of column_specs
  2034. my ($internal, @fld);
  2035. if($inf eq "-") {
  2036. $inf = "history";
  2037. }
  2038. if($outf eq "int" && $inf eq "current") {
  2039. $inf = "history";
  2040. Log3 $hash->{NAME}, 3, "Defining DbLog SVG-Plots with :CURRENT is deprecated. Please define DbLog SVG-Plots with :HISTORY instead of :CURRENT. (define <mySVG> SVG <DbLogDev>:<gplotfile>:HISTORY)";
  2041. }
  2042. if($outf eq "int") {
  2043. $outf = "-";
  2044. $internal = 1;
  2045. } elsif($outf eq "array"){
  2046. } elsif(lc($outf) eq "webchart") {
  2047. # redirect the get request to the chartQuery function
  2048. return chartQuery($hash, @_);
  2049. }
  2050. my @readings = ();
  2051. my (%sqlspec, %from_datetime, %to_datetime);
  2052. #uebergebenen Timestamp anpassen
  2053. #moegliche Formate: YYYY | YYYY-MM | YYYY-MM-DD | YYYY-MM-DD_HH24
  2054. $from =~ s/_/\ /g;
  2055. $to =~ s/_/\ /g;
  2056. %from_datetime = DbLog_explode_datetime($from, DbLog_explode_datetime("2000-01-01 00:00:00", ()));
  2057. %to_datetime = DbLog_explode_datetime($to, DbLog_explode_datetime("2099-01-01 00:00:00", ()));
  2058. $from = $from_datetime{datetime};
  2059. $to = $to_datetime{datetime};
  2060. my ($retval,$retvaldummy,$hour,$sql_timestamp, $sql_device, $sql_reading, $sql_value, $type, $event, $unit) = "";
  2061. my @ReturnArray;
  2062. my $writeout = 0;
  2063. my (@min, @max, @sum, @cnt, @lastv, @lastd, @mind, @maxd);
  2064. my (%tstamp, %lasttstamp, $out_tstamp, $out_value, $minval, $maxval, $deltacalc); #fuer delta-h/d Berechnung
  2065. #extract the Device:Reading arguments into @readings array
  2066. for(my $i = 0; $i < int(@a); $i++) {
  2067. @fld = split(":", $a[$i], 5);
  2068. $readings[$i][0] = $fld[0]; # Device
  2069. $readings[$i][1] = $fld[1]; # Reading
  2070. $readings[$i][2] = $fld[2]; # Default
  2071. $readings[$i][3] = $fld[3]; # function
  2072. $readings[$i][4] = $fld[4]; # regexp
  2073. $readings[$i][1] = "%" if(!$readings[$i][1] || length($readings[$i][1])==0); #falls Reading nicht gefuellt setze Joker
  2074. }
  2075. $dbh = $hash->{DBHP};
  2076. if ( !$dbh || not $dbh->ping ) {
  2077. # DB Session dead, try to reopen now !
  2078. return "Can't connect to database." if(!DbLog_ConnectPush($hash,1));
  2079. $dbh = $hash->{DBHP};
  2080. }
  2081. if( $hash->{PID} != $$ ) {
  2082. #create new connection for plotfork
  2083. $dbh->disconnect();
  2084. return "Can't connect to database." if(!DbLog_ConnectPush($hash,1));
  2085. $dbh = $hash->{DBHP};
  2086. }
  2087. #vorbereiten der DB-Abfrage, DB-Modell-abhaengig
  2088. if ($hash->{MODEL} eq "POSTGRESQL") {
  2089. $sqlspec{get_timestamp} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS')";
  2090. $sqlspec{from_timestamp} = "TO_TIMESTAMP('$from', 'YYYY-MM-DD HH24:MI:SS')";
  2091. $sqlspec{to_timestamp} = "TO_TIMESTAMP('$to', 'YYYY-MM-DD HH24:MI:SS')";
  2092. #$sqlspec{reading_clause} = "(DEVICE || '|' || READING)";
  2093. $sqlspec{order_by_hour} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24')";
  2094. $sqlspec{max_value} = "MAX(VALUE)";
  2095. $sqlspec{day_before} = "($sqlspec{from_timestamp} - INTERVAL '1 DAY')";
  2096. } elsif ($hash->{MODEL} eq "ORACLE") {
  2097. $sqlspec{get_timestamp} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS')";
  2098. $sqlspec{from_timestamp} = "TO_TIMESTAMP('$from', 'YYYY-MM-DD HH24:MI:SS')";
  2099. $sqlspec{to_timestamp} = "TO_TIMESTAMP('$to', 'YYYY-MM-DD HH24:MI:SS')";
  2100. $sqlspec{order_by_hour} = "TO_CHAR(TIMESTAMP, 'YYYY-MM-DD HH24')";
  2101. $sqlspec{max_value} = "MAX(VALUE)";
  2102. $sqlspec{day_before} = "DATE_SUB($sqlspec{from_timestamp},INTERVAL 1 DAY)";
  2103. } elsif ($hash->{MODEL} eq "MYSQL") {
  2104. $sqlspec{get_timestamp} = "DATE_FORMAT(TIMESTAMP, '%Y-%m-%d %H:%i:%s')";
  2105. $sqlspec{from_timestamp} = "STR_TO_DATE('$from', '%Y-%m-%d %H:%i:%s')";
  2106. $sqlspec{to_timestamp} = "STR_TO_DATE('$to', '%Y-%m-%d %H:%i:%s')";
  2107. $sqlspec{order_by_hour} = "DATE_FORMAT(TIMESTAMP, '%Y-%m-%d %H')";
  2108. $sqlspec{max_value} = "MAX(CAST(VALUE AS DECIMAL(20,8)))";
  2109. $sqlspec{day_before} = "DATE_SUB($sqlspec{from_timestamp},INTERVAL 1 DAY)";
  2110. } elsif ($hash->{MODEL} eq "SQLITE") {
  2111. $sqlspec{get_timestamp} = "TIMESTAMP";
  2112. $sqlspec{from_timestamp} = "'$from'";
  2113. $sqlspec{to_timestamp} = "'$to'";
  2114. $sqlspec{order_by_hour} = "strftime('%Y-%m-%d %H', TIMESTAMP)";
  2115. $sqlspec{max_value} = "MAX(VALUE)";
  2116. $sqlspec{day_before} = "date($sqlspec{from_timestamp},'-1 day')";
  2117. } else {
  2118. $sqlspec{get_timestamp} = "TIMESTAMP";
  2119. $sqlspec{from_timestamp} = "'$from'";
  2120. $sqlspec{to_timestamp} = "'$to'";
  2121. $sqlspec{order_by_hour} = "strftime('%Y-%m-%d %H', TIMESTAMP)";
  2122. $sqlspec{max_value} = "MAX(VALUE)";
  2123. $sqlspec{day_before} = "date($sqlspec{from_timestamp},'-1 day')";
  2124. }
  2125. if($outf =~ m/(all|array)/) {
  2126. $sqlspec{all} = ",TYPE,EVENT,UNIT";
  2127. $sqlspec{all_max} = ",MAX(TYPE) AS TYPE,MAX(EVENT) AS EVENT,MAX(UNIT) AS UNIT";
  2128. } else {
  2129. $sqlspec{all} = "";
  2130. $sqlspec{all_max} = "";
  2131. }
  2132. for(my $i=0; $i<int(@readings); $i++) {
  2133. # ueber alle Readings
  2134. # Variablen initialisieren
  2135. $min[$i] = (~0 >> 1);
  2136. $max[$i] = -(~0 >> 1);
  2137. $sum[$i] = 0;
  2138. $cnt[$i] = 0;
  2139. $lastv[$i] = 0;
  2140. $lastd[$i] = "undef";
  2141. $mind[$i] = "undef";
  2142. $maxd[$i] = "undef";
  2143. $minval = (~0 >> 1);
  2144. $maxval = -(~0 >> 1);
  2145. $deltacalc = 0;
  2146. if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) {
  2147. $deltacalc = 1;
  2148. }
  2149. my $stm;
  2150. my $stm2;
  2151. my $stmdelta;
  2152. $stm = "SELECT
  2153. MAX($sqlspec{get_timestamp}) AS TIMESTAMP,
  2154. MAX(DEVICE) AS DEVICE,
  2155. MAX(READING) AS READING,
  2156. $sqlspec{max_value}
  2157. $sqlspec{all_max} ";
  2158. $stm .= "FROM current " if($inf eq "current");
  2159. $stm .= "FROM history " if($inf eq "history");
  2160. $stm .= "WHERE 1=1 ";
  2161. $stm .= "AND DEVICE = '".$readings[$i]->[0]."' " if ($readings[$i]->[0] !~ m(\%));
  2162. $stm .= "AND DEVICE LIKE '".$readings[$i]->[0]."' " if(($readings[$i]->[0] !~ m(^\%$)) && ($readings[$i]->[0] =~ m(\%)));
  2163. $stm .= "AND READING = '".$readings[$i]->[1]."' " if ($readings[$i]->[1] !~ m(\%));
  2164. $stm .= "AND READING LIKE '".$readings[$i]->[1]."' " if(($readings[$i]->[1] !~ m(^%$)) && ($readings[$i]->[1] =~ m(\%)));
  2165. $stmdelta = $stm;
  2166. $stm .= "AND TIMESTAMP < $sqlspec{from_timestamp} ";
  2167. $stm .= "AND TIMESTAMP > $sqlspec{day_before} ";
  2168. $stm .= "UNION ALL ";
  2169. $stm2 = "SELECT
  2170. $sqlspec{get_timestamp},
  2171. DEVICE,
  2172. READING,
  2173. VALUE
  2174. $sqlspec{all} ";
  2175. $stm2 .= "FROM current " if($inf eq "current");
  2176. $stm2 .= "FROM history " if($inf eq "history");
  2177. $stm2 .= "WHERE 1=1 ";
  2178. $stm2 .= "AND DEVICE = '".$readings[$i]->[0]."' " if ($readings[$i]->[0] !~ m(\%));
  2179. $stm2 .= "AND DEVICE LIKE '".$readings[$i]->[0]."' " if(($readings[$i]->[0] !~ m(^\%$)) && ($readings[$i]->[0] =~ m(\%)));
  2180. $stm2 .= "AND READING = '".$readings[$i]->[1]."' " if ($readings[$i]->[1] !~ m(\%));
  2181. $stm2 .= "AND READING LIKE '".$readings[$i]->[1]."' " if(($readings[$i]->[1] !~ m(^%$)) && ($readings[$i]->[1] =~ m(\%)));
  2182. $stm2 .= "AND TIMESTAMP >= $sqlspec{from_timestamp} ";
  2183. $stm2 .= "AND TIMESTAMP < $sqlspec{to_timestamp} ";
  2184. $stm2 .= "ORDER BY TIMESTAMP";
  2185. if($deltacalc) {
  2186. $stmdelta .= "AND TIMESTAMP >= $sqlspec{from_timestamp} ";
  2187. $stmdelta .= "AND TIMESTAMP < $sqlspec{to_timestamp} ";
  2188. $stmdelta .= "GROUP BY $sqlspec{order_by_hour} " if($deltacalc);
  2189. $stmdelta .= "ORDER BY TIMESTAMP";
  2190. $stm .= $stmdelta;
  2191. } else {
  2192. $stm = $stm2;
  2193. }
  2194. Log3 $hash->{NAME}, 4, "Processing Statement: $stm";
  2195. my $sth= $dbh->prepare($stm) ||
  2196. return "Cannot prepare statement $stm: $DBI::errstr";
  2197. my $rc= $sth->execute() ||
  2198. return "Cannot execute statement $stm: $DBI::errstr";
  2199. if($outf =~ m/(all|array)/) {
  2200. $sth->bind_columns(undef, \$sql_timestamp, \$sql_device, \$sql_reading, \$sql_value, \$type, \$event, \$unit);
  2201. }
  2202. else {
  2203. $sth->bind_columns(undef, \$sql_timestamp, \$sql_device, \$sql_reading, \$sql_value);
  2204. }
  2205. if ($outf =~ m/(all)/) {
  2206. $retval .= "Timestamp: Device, Type, Event, Reading, Value, Unit\n";
  2207. $retval .= "=====================================================\n";
  2208. }
  2209. while($sth->fetch()) {
  2210. ############ Auswerten des 5. Parameters: Regexp ###################
  2211. # die Regexep wird vor der Function ausgewertet und der Wert im Feld
  2212. # Value angepasst.
  2213. ####################################################################
  2214. if($readings[$i]->[4]) {
  2215. #evaluate
  2216. my $val = $sql_value;
  2217. my $ts = $sql_timestamp;
  2218. eval("$readings[$i]->[4]");
  2219. $sql_value = $val;
  2220. $sql_timestamp = $ts;
  2221. if($@) {Log3 $hash->{NAME}, 3, "DbLog: Error in inline function: <".$readings[$i]->[4].">, Error: $@";}
  2222. }
  2223. if($sql_timestamp lt $from && $deltacalc) {
  2224. if(Scalar::Util::looks_like_number($sql_value)){
  2225. #nur setzen wenn nummerisch
  2226. $minval = $sql_value if($sql_value < $minval);
  2227. $maxval = $sql_value if($sql_value > $maxval);
  2228. $lastv[$i] = $sql_value;
  2229. }
  2230. } else {
  2231. $writeout = 0;
  2232. $out_value = "";
  2233. $out_tstamp = "";
  2234. $retvaldummy = "";
  2235. if($readings[$i]->[4]) {
  2236. $out_tstamp = $sql_timestamp;
  2237. $writeout=1 if(!$deltacalc);
  2238. }
  2239. ############ Auswerten des 4. Parameters: function ###################
  2240. if($readings[$i]->[3] && $readings[$i]->[3] eq "int") {
  2241. #nur den integerwert uebernehmen falls zb value=15°C
  2242. $out_value = $1 if($sql_value =~ m/^(\d+).*/o);
  2243. $out_tstamp = $sql_timestamp;
  2244. $writeout=1;
  2245. } elsif ($readings[$i]->[3] && $readings[$i]->[3] =~ m/^int(\d+).*/o) {
  2246. #Uebernehme den Dezimalwert mit den angegebenen Stellen an Nachkommastellen
  2247. $out_value = $1 if($sql_value =~ m/^([-\.\d]+).*/o);
  2248. $out_tstamp = $sql_timestamp;
  2249. $writeout=1;
  2250. } elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-ts" && lc($sql_value) !~ m(ignore)) {
  2251. #Berechung der vergangen Sekunden seit dem letten Logeintrag
  2252. #zb. die Zeit zwischen on/off
  2253. my @a = split("[- :]", $sql_timestamp);
  2254. my $akt_ts = mktime($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0]-1900,0,0,-1);
  2255. if($lastd[$i] ne "undef") {
  2256. @a = split("[- :]", $lastd[$i]);
  2257. }
  2258. my $last_ts = mktime($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0]-1900,0,0,-1);
  2259. $out_tstamp = $sql_timestamp;
  2260. $out_value = sprintf("%02d", $akt_ts - $last_ts);
  2261. if(lc($sql_value) =~ m(hide)){$writeout=0;} else {$writeout=1;}
  2262. } elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-h") {
  2263. #Berechnung eines Stundenwertes
  2264. %tstamp = DbLog_explode_datetime($sql_timestamp, ());
  2265. if($lastd[$i] eq "undef") {
  2266. %lasttstamp = DbLog_explode_datetime($sql_timestamp, ());
  2267. $lasttstamp{hour} = "00";
  2268. } else {
  2269. %lasttstamp = DbLog_explode_datetime($lastd[$i], ());
  2270. }
  2271. # 04 01
  2272. # 06 23
  2273. if("$tstamp{hour}" ne "$lasttstamp{hour}") {
  2274. # Aenderung der stunde, Berechne Delta
  2275. #wenn die Stundendifferenz größer 1 ist muss ein Dummyeintrag erstellt werden
  2276. $retvaldummy = "";
  2277. if(($tstamp{hour}-$lasttstamp{hour}) > 1) {
  2278. for (my $j=$lasttstamp{hour}+1; $j < $tstamp{hour}; $j++) {
  2279. $out_value = "0";
  2280. $hour = $j;
  2281. $hour = '0'.$j if $j<10;
  2282. $cnt[$i]++;
  2283. $out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00");
  2284. if ($outf =~ m/(all)/) {
  2285. # Timestamp: Device, Type, Event, Reading, Value, Unit
  2286. $retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
  2287. } elsif ($outf =~ m/(array)/) {
  2288. push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
  2289. } else {
  2290. $out_tstamp =~ s/\ /_/g; #needed by generating plots
  2291. $retvaldummy .= "$out_tstamp $out_value\n";
  2292. }
  2293. }
  2294. }
  2295. if(($tstamp{hour}-$lasttstamp{hour}) < 0) {
  2296. for (my $j=0; $j < $tstamp{hour}; $j++) {
  2297. $out_value = "0";
  2298. $hour = $j;
  2299. $hour = '0'.$j if $j<10;
  2300. $cnt[$i]++;
  2301. $out_tstamp = DbLog_implode_datetime($tstamp{year}, $tstamp{month}, $tstamp{day}, $hour, "30", "00");
  2302. if ($outf =~ m/(all)/) {
  2303. # Timestamp: Device, Type, Event, Reading, Value, Unit
  2304. $retvaldummy .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
  2305. } elsif ($outf =~ m/(array)/) {
  2306. push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
  2307. } else {
  2308. $out_tstamp =~ s/\ /_/g; #needed by generating plots
  2309. $retvaldummy .= "$out_tstamp $out_value\n";
  2310. }
  2311. }
  2312. }
  2313. $out_value = sprintf("%g", $maxval - $minval);
  2314. $sum[$i] += $out_value;
  2315. $cnt[$i]++;
  2316. $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00");
  2317. #$minval = (~0 >> 1);
  2318. $minval = $maxval;
  2319. # $maxval = -(~0 >> 1);
  2320. $writeout=1;
  2321. }
  2322. } elsif ($readings[$i]->[3] && $readings[$i]->[3] eq "delta-d") {
  2323. #Berechnung eines Tageswertes
  2324. %tstamp = DbLog_explode_datetime($sql_timestamp, ());
  2325. if($lastd[$i] eq "undef") {
  2326. %lasttstamp = DbLog_explode_datetime($sql_timestamp, ());
  2327. } else {
  2328. %lasttstamp = DbLog_explode_datetime($lastd[$i], ());
  2329. }
  2330. if("$tstamp{day}" ne "$lasttstamp{day}") {
  2331. # Aenderung des Tages, Berechne Delta
  2332. $out_value = sprintf("%g", $maxval - $minval);
  2333. $sum[$i] += $out_value;
  2334. $cnt[$i]++;
  2335. $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "12", "00", "00");
  2336. # $minval = (~0 >> 1);
  2337. $minval = $maxval;
  2338. # $maxval = -(~0 >> 1);
  2339. $writeout=1;
  2340. }
  2341. } else {
  2342. $out_value = $sql_value;
  2343. $out_tstamp = $sql_timestamp;
  2344. $writeout=1;
  2345. }
  2346. # Wenn Attr SuppressUndef gesetzt ist, dann ausfiltern aller undef-Werte
  2347. $writeout = 0 if (!defined($sql_value) && AttrVal($hash->{NAME}, "suppressUndef", 0));
  2348. ###################### Ausgabe ###########################
  2349. if($writeout) {
  2350. if ($outf =~ m/(all)/) {
  2351. # Timestamp: Device, Type, Event, Reading, Value, Unit
  2352. $retval .= sprintf("%s: %s, %s, %s, %s, %s, %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
  2353. $retval .= $retvaldummy;
  2354. } elsif ($outf =~ m/(array)/) {
  2355. push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
  2356. } else {
  2357. $out_tstamp =~ s/\ /_/g; #needed by generating plots
  2358. $retval .= "$out_tstamp $out_value\n";
  2359. $retval .= $retvaldummy;
  2360. }
  2361. }
  2362. if(Scalar::Util::looks_like_number($sql_value)){
  2363. #nur setzen wenn nummerisch
  2364. if($deltacalc) {
  2365. if(Scalar::Util::looks_like_number($out_value)){
  2366. if($out_value < $min[$i]) {
  2367. $min[$i] = $out_value;
  2368. $mind[$i] = $out_tstamp;
  2369. }
  2370. if($out_value > $max[$i]) {
  2371. $max[$i] = $out_value;
  2372. $maxd[$i] = $out_tstamp;
  2373. }
  2374. }
  2375. $maxval = $sql_value;
  2376. } else {
  2377. if($sql_value < $min[$i]) {
  2378. $min[$i] = $sql_value;
  2379. $mind[$i] = $sql_timestamp;
  2380. }
  2381. if($sql_value > $max[$i]) {
  2382. $max[$i] = $sql_value;
  2383. $maxd[$i] = $sql_timestamp;
  2384. }
  2385. $sum[$i] += $sql_value;
  2386. $minval = $sql_value if($sql_value < $minval);
  2387. $maxval = $sql_value if($sql_value > $maxval);
  2388. }
  2389. } else {
  2390. $min[$i] = 0;
  2391. $max[$i] = 0;
  2392. $sum[$i] = 0;
  2393. $minval = 0;
  2394. $maxval = 0;
  2395. }
  2396. if(!$deltacalc) {
  2397. $cnt[$i]++;
  2398. $lastv[$i] = $sql_value;
  2399. } else {
  2400. $lastv[$i] = $out_value if($out_value);
  2401. }
  2402. $lastd[$i] = $sql_timestamp;
  2403. }
  2404. } #while fetchrow
  2405. ######## den letzten Abschlusssatz rausschreiben ##########
  2406. if($readings[$i]->[3] && ($readings[$i]->[3] eq "delta-h" || $readings[$i]->[3] eq "delta-d")) {
  2407. if($lastd[$i] eq "undef") {
  2408. $out_value = "0";
  2409. $out_tstamp = DbLog_implode_datetime($from_datetime{year}, $from_datetime{month}, $from_datetime{day}, $from_datetime{hour}, "30", "00") if($readings[$i]->[3] eq "delta-h");
  2410. $out_tstamp = DbLog_implode_datetime($from_datetime{year}, $from_datetime{month}, $from_datetime{day}, "12", "00", "00") if($readings[$i]->[3] eq "delta-d");
  2411. } else {
  2412. %lasttstamp = DbLog_explode_datetime($lastd[$i], ());
  2413. $out_value = sprintf("%g", $maxval - $minval);
  2414. $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, $lasttstamp{hour}, "30", "00") if($readings[$i]->[3] eq "delta-h");
  2415. $out_tstamp = DbLog_implode_datetime($lasttstamp{year}, $lasttstamp{month}, $lasttstamp{day}, "12", "00", "00") if($readings[$i]->[3] eq "delta-d");
  2416. }
  2417. $sum[$i] += $out_value;
  2418. $cnt[$i]++;
  2419. if($outf =~ m/(all)/) {
  2420. $retval .= sprintf("%s: %s %s %s %s %s %s\n", $out_tstamp, $sql_device, $type, $event, $sql_reading, $out_value, $unit);
  2421. } elsif ($outf =~ m/(array)/) {
  2422. push(@ReturnArray, {"tstamp" => $out_tstamp, "device" => $sql_device, "type" => $type, "event" => $event, "reading" => $sql_reading, "value" => $out_value, "unit" => $unit});
  2423. } else {
  2424. $out_tstamp =~ s/\ /_/g; #needed by generating plots
  2425. $retval .= "$out_tstamp $out_value\n";
  2426. }
  2427. }
  2428. # DatenTrenner setzen
  2429. $retval .= "#$readings[$i]->[0]";
  2430. $retval .= ":";
  2431. $retval .= "$readings[$i]->[1]" if($readings[$i]->[1]);
  2432. $retval .= ":";
  2433. $retval .= "$readings[$i]->[2]" if($readings[$i]->[2]);
  2434. $retval .= ":";
  2435. $retval .= "$readings[$i]->[3]" if($readings[$i]->[3]);
  2436. $retval .= ":";
  2437. $retval .= "$readings[$i]->[4]" if($readings[$i]->[4]);
  2438. $retval .= "\n";
  2439. } #for @readings
  2440. #Ueberfuehren der gesammelten Werte in die globale Variable %data
  2441. for(my $j=0; $j<int(@readings); $j++) {
  2442. my $k = $j+1;
  2443. $data{"min$k"} = $min[$j];
  2444. $data{"max$k"} = $max[$j];
  2445. $data{"avg$k"} = $cnt[$j] ? sprintf("%0.2f", $sum[$j]/$cnt[$j]) : 0;
  2446. $data{"sum$k"} = $sum[$j];
  2447. $data{"cnt$k"} = $cnt[$j];
  2448. $data{"currval$k"} = $lastv[$j];
  2449. $data{"currdate$k"} = $lastd[$j];
  2450. $data{"mindate$k"} = $mind[$j];
  2451. $data{"maxdate$k"} = $maxd[$j];
  2452. }
  2453. #cleanup (plotfork) connection
  2454. $dbh->disconnect() if( $hash->{PID} != $$ );
  2455. if($internal) {
  2456. $internal_data = \$retval;
  2457. return undef;
  2458. } elsif($outf =~ m/(array)/) {
  2459. return @ReturnArray;
  2460. } else {
  2461. $retval = Encode::encode_utf8($retval) if($utf8);
  2462. # Log3 $name, 5, "DbLog $name -> Result of get:\n$retval";
  2463. return $retval;
  2464. }
  2465. }
  2466. ##########################################################################
  2467. #
  2468. # Ermittlung von DB-Parametern für configCheck
  2469. #
  2470. ##########################################################################
  2471. sub DbLog_configcheck($) {
  2472. my ($hash)= @_;
  2473. my $name = $hash->{NAME};
  2474. my $dbmodel = $hash->{MODEL};
  2475. my $dbconn = $hash->{dbconn};
  2476. my $dbname = (split(/;|=/, $dbconn))[1];
  2477. my ($check, $rec);
  2478. # Connection und Encoding check
  2479. my (@ce,@se);
  2480. my ($chutf8mod,$chutf8dat);
  2481. if($dbmodel =~ /MYSQL/) {
  2482. @ce = DbLog_sqlget($hash,"SHOW VARIABLES LIKE 'character_set_connection'");
  2483. $chutf8mod = @ce?uc($ce[1]):"no result";
  2484. @se = DbLog_sqlget($hash,"SHOW VARIABLES LIKE 'character_set_database'");
  2485. $chutf8dat = @se?uc($se[1]):"no result";
  2486. if($chutf8mod eq $chutf8dat) {
  2487. $rec = "settings o.k.";
  2488. } else {
  2489. $rec = "Both encodings should be identical. You can adjust the usage of UTF8 connection by setting the UTF8 parameter in file '$hash->{CONFIGURATION}' to the right value. ";
  2490. }
  2491. }
  2492. if($dbmodel =~ /POSTGRESQL/) {
  2493. @ce = DbLog_sqlget($hash,"SHOW CLIENT_ENCODING");
  2494. $chutf8mod = @ce?uc($ce[0]):"no result";
  2495. @se = DbLog_sqlget($hash,"select character_set_name from information_schema.character_sets");
  2496. $chutf8dat = @se?uc($se[0]):"no result";
  2497. if($chutf8mod eq $chutf8dat) {
  2498. $rec = "settings o.k.";
  2499. } else {
  2500. $rec = "This is only an information. PostgreSQL supports automatic character set conversion between server and client for certain character set combinations. The conversion information is stored in the pg_conversion system catalog. PostgreSQL comes with some predefined conversions.";
  2501. }
  2502. }
  2503. $check = "<html>";
  2504. $check .= "<u><b>Result of connection check</u></b><br><br>";
  2505. if(@ce && @se) {
  2506. $check .= "Connection to database $dbname successfully done. <br>";
  2507. $check .= "<b>Recommendation:</b> settings o.k. <br><br>";
  2508. }
  2509. if(!@ce || !@se) {
  2510. $check .= "Connection to database was not successful. <br>";
  2511. $check .= "<b>Recommendation:</b> Plese see logfile for further information. <br><br>";
  2512. $check .= "</html>";
  2513. return $check;
  2514. }
  2515. $check .= "<u><b>Result of encoding check</u></b><br><br>";
  2516. $check .= "Encoding used by Client (connection): $chutf8mod <br>";
  2517. $check .= "Encoding used by DB $dbname: $chutf8dat <br>";
  2518. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2519. # Check Betriebsmodus
  2520. my $mode = $hash->{MODE};
  2521. my $sfx = AttrVal("global", "language", "EN");
  2522. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  2523. $check .= "<u><b>Result of logmode check</u></b><br><br>";
  2524. $check .= "Logmode of DbLog-device $name is: $mode <br>";
  2525. if($mode =~ /asynchronous/) {
  2526. $rec = "settings o.k.";
  2527. } else {
  2528. $rec = "Switch $name to the asynchronous logmode by setting the 'asyncMode' attribute. The advantage of this mode is to log events non-blocking. <br>";
  2529. $rec .= "There are attributes 'syncInterval' and 'cacheLimit' relevant for this working mode. <br>";
  2530. $rec .= "Please refer to Commandref attributes for further informations.";
  2531. }
  2532. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2533. # Check Spaltenbreite history
  2534. my (@sr_dev,@sr_typ,@sr_evt,@sr_rdg,@sr_val,@sr_unt);
  2535. my ($cdat_dev,$cdat_typ,$cdat_evt,$cdat_rdg,$cdat_val,$cdat_unt);
  2536. my ($cmod_dev,$cmod_typ,$cmod_evt,$cmod_rdg,$cmod_val,$cmod_unt);
  2537. if($dbmodel =~ /MYSQL/) {
  2538. @sr_dev = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='DEVICE'");
  2539. @sr_typ = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='TYPE'");
  2540. @sr_evt = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='EVENT'");
  2541. @sr_rdg = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='READING'");
  2542. @sr_val = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='VALUE'");
  2543. @sr_unt = DbLog_sqlget($hash,"SHOW FIELDS FROM history where FIELD='UNIT'");
  2544. }
  2545. if($dbmodel =~ /POSTGRESQL/) {
  2546. @sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='device'");
  2547. @sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='type'");
  2548. @sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='event'");
  2549. @sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='reading'");
  2550. @sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='value'");
  2551. @sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='history' and column_name='unit'");
  2552. }
  2553. $cdat_dev = @sr_dev?($sr_dev[1]):"no result";
  2554. $cdat_dev =~ tr/varchar\(|\)//d if($cdat_dev ne "no result");
  2555. $cdat_typ = @sr_typ?($sr_typ[1]):"no result";
  2556. $cdat_typ =~ tr/varchar\(|\)//d if($cdat_typ ne "no result");
  2557. $cdat_evt = @sr_evt?($sr_evt[1]):"no result";
  2558. $cdat_evt =~ tr/varchar\(|\)//d if($cdat_evt ne "no result");
  2559. $cdat_rdg = @sr_rdg?($sr_rdg[1]):"no result";
  2560. $cdat_rdg =~ tr/varchar\(|\)//d if($cdat_rdg ne "no result");
  2561. $cdat_val = @sr_val?($sr_val[1]):"no result";
  2562. $cdat_val =~ tr/varchar\(|\)//d if($cdat_val ne "no result");
  2563. $cdat_unt = @sr_unt?($sr_unt[1]):"no result";
  2564. $cdat_unt =~ tr/varchar\(|\)//d if($cdat_unt ne "no result");
  2565. $cmod_dev = $hash->{HELPER}{DEVICECOL};
  2566. $cmod_typ = $hash->{HELPER}{TYPECOL};
  2567. $cmod_evt = $hash->{HELPER}{EVENTCOL};
  2568. $cmod_rdg = $hash->{HELPER}{READINGCOL};
  2569. $cmod_val = $hash->{HELPER}{VALUECOL};
  2570. $cmod_unt = $hash->{HELPER}{UNITCOL};
  2571. if($cdat_dev >= $cmod_dev && $cdat_typ >= $cmod_typ && $cdat_evt >= $cmod_evt && $cdat_rdg >= $cmod_rdg && $cdat_val >= $cmod_val && $cdat_unt >= $cmod_unt) {
  2572. $rec = "settings o.k.";
  2573. } else {
  2574. $rec = "The relation between column width in table history and the field width used in device $name don't meet the requirements. ";
  2575. $rec .= "Please make sure that the width of database field definition is equal or larger than the field width used by the module. Compare the given results.<br>";
  2576. $rec .= "Currently the default values for field width are: <br><br>";
  2577. $rec .= "DEVICE: $columns{DEVICE} <br>";
  2578. $rec .= "TYPE: $columns{TYPE} <br>";
  2579. $rec .= "EVENT: $columns{EVENT} <br>";
  2580. $rec .= "READING: $columns{READING} <br>";
  2581. $rec .= "VALUE: $columns{VALUE} <br>";
  2582. $rec .= "UNIT: $columns{UNIT} <br><br>";
  2583. $rec .= "You can change the column width in database by a statement like <b>'alter table history modify VALUE varchar(128);</b>' (example for changing field 'VALUE'). ";
  2584. $rec .= "You can do it for example by executing 'sqlCMD' in DbRep or in a SQL-Editor of your choice. (switch $name to asynchron mode for non-blocking). <br>";
  2585. $rec .= "The field width used by the module can be adjusted by attributes 'colEvent', 'colReading', 'colValue',";
  2586. }
  2587. $check .= "<u><b>Result of table 'history' check</u></b><br><br>";
  2588. $check .= "Column width set in DB $dbname.history: 'DEVICE' = $cdat_dev, 'TYPE' = $cdat_typ, 'EVENT' = $cdat_evt, 'READING' = $cdat_rdg, 'VALUE' = $cdat_val, 'UNIT' = $cdat_unt <br>";
  2589. $check .= "Column width used by $name: 'DEVICE' = $cmod_dev, 'TYPE' = $cmod_typ, 'EVENT' = $cmod_evt, 'READING' = $cmod_rdg, 'VALUE' = $cmod_val, 'UNIT' = $cmod_unt <br>";
  2590. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2591. # Check Spaltenbreite current
  2592. if($dbmodel =~ /MYSQL/) {
  2593. @sr_dev = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='DEVICE'");
  2594. @sr_typ = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='TYPE'");
  2595. @sr_evt = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='EVENT'");
  2596. @sr_rdg = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='READING'");
  2597. @sr_val = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='VALUE'");
  2598. @sr_unt = DbLog_sqlget($hash,"SHOW FIELDS FROM current where FIELD='UNIT'");
  2599. }
  2600. if($dbmodel =~ /POSTGRESQL/) {
  2601. @sr_dev = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='device'");
  2602. @sr_typ = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='type'");
  2603. @sr_evt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='event'");
  2604. @sr_rdg = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='reading'");
  2605. @sr_val = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='value'");
  2606. @sr_unt = DbLog_sqlget($hash,"select column_name,character_maximum_length from information_schema.columns where table_name='current' and column_name='unit'");
  2607. }
  2608. $cdat_dev = @sr_dev?($sr_dev[1]):"no result";
  2609. $cdat_dev =~ tr/varchar\(|\)//d if($cdat_dev ne "no result");
  2610. $cdat_typ = @sr_typ?($sr_typ[1]):"no result";
  2611. $cdat_typ =~ tr/varchar\(|\)//d if($cdat_typ ne "no result");
  2612. $cdat_evt = @sr_evt?($sr_evt[1]):"no result";
  2613. $cdat_evt =~ tr/varchar\(|\)//d if($cdat_evt ne "no result");
  2614. $cdat_rdg = @sr_rdg?($sr_rdg[1]):"no result";
  2615. $cdat_rdg =~ tr/varchar\(|\)//d if($cdat_rdg ne "no result");
  2616. $cdat_val = @sr_val?($sr_val[1]):"no result";
  2617. $cdat_val =~ tr/varchar\(|\)//d if($cdat_val ne "no result");
  2618. $cdat_unt = @sr_unt?($sr_unt[1]):"no result";
  2619. $cdat_unt =~ tr/varchar\(|\)//d if($cdat_unt ne "no result");
  2620. $cmod_dev = $hash->{HELPER}{DEVICECOL};
  2621. $cmod_typ = $hash->{HELPER}{TYPECOL};
  2622. $cmod_evt = $hash->{HELPER}{EVENTCOL};
  2623. $cmod_rdg = $hash->{HELPER}{READINGCOL};
  2624. $cmod_val = $hash->{HELPER}{VALUECOL};
  2625. $cmod_unt = $hash->{HELPER}{UNITCOL};
  2626. if($cdat_dev >= $cmod_dev && $cdat_typ >= $cmod_typ && $cdat_evt >= $cmod_evt && $cdat_rdg >= $cmod_rdg && $cdat_val >= $cmod_val && $cdat_unt >= $cmod_unt) {
  2627. $rec = "settings o.k.";
  2628. } else {
  2629. $rec = "The relation between column width in table current and the field width used in device $name don't meet the requirements. ";
  2630. $rec .= "Please make sure that the width of database field definition is equal or larger than the field width used by the module. Compare the given results.<br>";
  2631. $rec .= "Currently the default values for field width are: <br><br>";
  2632. $rec .= "DEVICE: $columns{DEVICE} <br>";
  2633. $rec .= "TYPE: $columns{TYPE} <br>";
  2634. $rec .= "EVENT: $columns{EVENT} <br>";
  2635. $rec .= "READING: $columns{READING} <br>";
  2636. $rec .= "VALUE: $columns{VALUE} <br>";
  2637. $rec .= "UNIT: $columns{UNIT} <br><br>";
  2638. $rec .= "You can change the column width in database by a statement like <b>'alter table current modify VALUE varchar(128);</b>' (example for changing field 'VALUE'). ";
  2639. $rec .= "You can do it for example by executing 'sqlCMD' in DbRep or in a SQL-Editor of your choice. (switch $name to asynchron mode for non-blocking). <br>";
  2640. $rec .= "The field width used by the module can be adjusted by attributes 'colEvent', 'colReading', 'colValue',";
  2641. }
  2642. $check .= "<u><b>Result of table 'current' check</u></b><br><br>";
  2643. $check .= "Column width set in DB $dbname.current: 'DEVICE' = $cdat_dev, 'TYPE' = $cdat_typ, 'EVENT' = $cdat_evt, 'READING' = $cdat_rdg, 'VALUE' = $cdat_val, 'UNIT' = $cdat_unt <br>";
  2644. $check .= "Column width used by $name: 'DEVICE' = $cmod_dev, 'TYPE' = $cmod_typ, 'EVENT' = $cmod_evt, 'READING' = $cmod_rdg, 'VALUE' = $cmod_val, 'UNIT' = $cmod_unt <br>";
  2645. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2646. # Check Vorhandensein Search_Idx mit den empfohlenen Spalten
  2647. my (@six,@six_dev,@six_rdg,@six_tsp);
  2648. my ($idef,$idef_dev,$idef_rdg,$idef_tsp);
  2649. $check .= "<u><b>Result of check 'Search_Idx' availability</u></b><br><br>";
  2650. if($dbmodel =~ /MYSQL/) {
  2651. @six = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Search_Idx'");
  2652. if (!@six) {
  2653. $check .= "The index 'Search_Idx' is missing. <br>";
  2654. $rec = "You can create the index by executing statement 'CREATE INDEX Search_Idx ON `history` (DEVICE, READING, TIMESTAMP) USING BTREE;' <br>";
  2655. $rec .= "Depending on your database size this command may running a long time. <br>";
  2656. $rec .= "Please make sure the device '$name' is operating in asynchronous mode to avoid FHEM from blocking when creating the index. <br>";
  2657. $rec .= "<b>Note:</b> If you have just created another index which covers the same fields and order as suggested (e.g. a primary key) you don't need to create the 'Search_Idx' as well ! <br>";
  2658. } else {
  2659. @six_dev = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Search_Idx' and Column_name='DEVICE'");
  2660. @six_rdg = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Search_Idx' and Column_name='READING'");
  2661. @six_tsp = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Search_Idx' and Column_name='TIMESTAMP'");
  2662. if (@six_dev && @six_rdg && @six_tsp) {
  2663. $check .= "Index 'Search_Idx' exists and contains the recommended fields 'DEVICE', 'READING', 'TIMESTAMP'. <br>";
  2664. $rec = "settings o.k.";
  2665. } else {
  2666. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'DEVICE'. <br>" if (!@six_dev);
  2667. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'READING'. <br>" if (!@six_rdg);
  2668. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'TIMESTAMP'. <br>" if (!@six_tsp);
  2669. $rec = "The index should contain the fields 'DEVICE', 'READING', 'TIMESTAMP'. ";
  2670. $rec .= "You can change the index by executing e.g. <br>";
  2671. $rec .= "'ALTER TABLE `history` DROP INDEX `Search_Idx`, ADD INDEX `Search_Idx` (`DEVICE`, `READING`, `TIMESTAMP`) USING BTREE;' <br>";
  2672. $rec .= "Depending on your database size this command may running a long time. <br>";
  2673. }
  2674. }
  2675. }
  2676. if($dbmodel =~ /POSTGRESQL/) {
  2677. @six = DbLog_sqlget($hash,"SELECT * FROM pg_indexes WHERE tablename='history' and indexname ='Search_Idx'");
  2678. if (!@six) {
  2679. $check .= "The index 'Search_Idx' is missing. <br>";
  2680. $rec = "You can create the index by executing statement 'CREATE INDEX \"Search_Idx\" ON history USING btree (device, reading, \"timestamp\")' <br>";
  2681. $rec .= "Depending on your database size this command may running a long time. <br>";
  2682. $rec .= "Please make sure the device '$name' is operating in asynchronous mode to avoid FHEM from blocking when creating the index. <br>";
  2683. $rec .= "<b>Note:</b> If you have just created another index which covers the same fields and order as suggested (e.g. a primary key) you don't need to create the 'Search_Idx' as well ! <br>";
  2684. } else {
  2685. $idef = $six[4];
  2686. $idef_dev = 1 if($idef =~ /device/);
  2687. $idef_rdg = 1 if($idef =~ /reading/);
  2688. $idef_tsp = 1 if($idef =~ /timestamp/);
  2689. if ($idef_dev && $idef_rdg && $idef_tsp) {
  2690. $check .= "Index 'Search_Idx' exists and contains the recommended fields 'DEVICE', 'READING', 'TIMESTAMP'. <br>";
  2691. $rec = "settings o.k.";
  2692. } else {
  2693. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'DEVICE'. <br>" if (!$idef_dev);
  2694. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'READING'. <br>" if (!$idef_rdg);
  2695. $check .= "Index 'Search_Idx' exists but doesn't contain recommended field 'TIMESTAMP'. <br>" if (!$idef_tsp);
  2696. $rec = "The index should contain the fields 'DEVICE', 'READING', 'TIMESTAMP'. ";
  2697. $rec .= "You can change the index by executing e.g. <br>";
  2698. $rec .= "'DROP INDEX \"Search_Idx\"; CREATE INDEX \"Search_Idx\" ON history USING btree (device, reading, \"timestamp\")' <br>";
  2699. $rec .= "Depending on your database size this command may running a long time. <br>";
  2700. }
  2701. }
  2702. }
  2703. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2704. # Check Index Report_Idx für DbRep-Device falls DbRep verwendet wird
  2705. my ($dbrp,$irep,);
  2706. my (@dix,@dix_rdg,@dix_tsp,$irep_rdg,$irep_tsp);
  2707. my $isused = 0;
  2708. my @repdvs = devspec2array("TYPE=DbRep");
  2709. $check .= "<u><b>Result of check 'Report_Idx' availability for DbRep-devices</u></b><br><br>";
  2710. foreach (@repdvs) {
  2711. $dbrp = $_;
  2712. if(!$defs{$dbrp}) {
  2713. Log3 ($name, 2, "DbLog $name -> Device '$dbrp' found by configCheck doesn't exist !");
  2714. next;
  2715. }
  2716. if ($defs{$dbrp}->{DEF} eq $name) {
  2717. # DbRep Device verwendet aktuelles DbLog-Device
  2718. Log3 ($name, 5, "DbLog $name -> DbRep-Device '$dbrp' uses $name.");
  2719. $isused = 1;
  2720. }
  2721. }
  2722. if ($isused) {
  2723. if($dbmodel =~ /MYSQL/) {
  2724. @dix = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Report_Idx'");
  2725. if (!@dix) {
  2726. $check .= "You use at least one DbRep-device assigned to $name, but the recommended index 'Report_Idx' is missing. <br>";
  2727. $rec = "You can create the index by executing statement 'CREATE INDEX Report_Idx ON `history` (TIMESTAMP, READING) USING BTREE;' <br>";
  2728. $rec .= "Depending on your database size this command may running a long time. <br>";
  2729. $rec .= "Please make sure the device '$name' is operating in asynchronous mode to avoid FHEM from blocking when creating the index. <br>";
  2730. $rec .= "<b>Note:</b> If you have just created another index which covers the same fields and order as suggested (e.g. a primary key) you don't need to create the 'Report_Idx' as well ! <br>";
  2731. } else {
  2732. @dix_rdg = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Report_Idx' and Column_name='READING'");
  2733. @dix_tsp = DbLog_sqlget($hash,"SHOW INDEX FROM history where Key_name='Report_Idx' and Column_name='TIMESTAMP'");
  2734. if (@dix_rdg && @dix_tsp) {
  2735. $check .= "You use at least one DbRep-device assigned to $name. ";
  2736. $check .= "Index 'Report_Idx' exists and contains the recommended fields 'TIMESTAMP', 'READING'. <br>";
  2737. $rec = "settings o.k.";
  2738. } else {
  2739. $check .= "You use at least one DbRep-device assigned to $name. ";
  2740. $check .= "Index 'Report_Idx' exists but doesn't contain recommended field 'READING'. <br>" if (!@dix_rdg);
  2741. $check .= "Index 'Report_Idx' exists but doesn't contain recommended field 'TIMESTAMP'. <br>" if (!@dix_tsp);
  2742. $rec = "The index should contain the fields 'TIMESTAMP', 'READING'. ";
  2743. $rec .= "You can change the index by executing e.g. <br>";
  2744. $rec .= "'ALTER TABLE `history` DROP INDEX `Report_Idx`, ADD INDEX `Report_Idx` (`TIMESTAMP`, `READING`) USING BTREE' <br>";
  2745. $rec .= "Depending on your database size this command may running a long time. <br>";
  2746. }
  2747. }
  2748. }
  2749. if($dbmodel =~ /POSTGRESQL/) {
  2750. @dix = DbLog_sqlget($hash,"SELECT * FROM pg_indexes WHERE tablename='history' and indexname ='Report_Idx'");
  2751. if (!@dix) {
  2752. $check .= "You use at least one DbRep-device assigned to $name, but the recommended index 'Report_Idx' is missing. <br>";
  2753. $rec = "You can create the index by executing statement 'CREATE INDEX \"Report_Idx\" ON history USING btree (\"timestamp\", reading)' <br>";
  2754. $rec .= "Depending on your database size this command may running a long time. <br>";
  2755. $rec .= "Please make sure the device '$name' is operating in asynchronous mode to avoid FHEM from blocking when creating the index. <br>";
  2756. $rec .= "<b>Note:</b> If you have just created another index which covers the same fields and order as suggested (e.g. a primary key) you don't need to create the 'Report_Idx' as well ! <br>";
  2757. } else {
  2758. $irep = $dix[4];
  2759. $irep_rdg = 1 if($irep =~ /reading/);
  2760. $irep_tsp = 1 if($irep =~ /timestamp/);
  2761. if ($irep_rdg && $irep_tsp) {
  2762. $check .= "Index 'Report_Idx' exists and contains the recommended fields 'TIMESTAMP', 'READING'. <br>";
  2763. $rec = "settings o.k.";
  2764. } else {
  2765. $check .= "Index 'Report_Idx' exists but doesn't contain recommended field 'READING'. <br>" if (!$irep_rdg);
  2766. $check .= "Index 'Report_Idx' exists but doesn't contain recommended field 'TIMESTAMP'. <br>" if (!$irep_tsp);
  2767. $rec = "The index should contain the fields 'TIMESTAMP', 'READING'. ";
  2768. $rec .= "You can change the index by executing e.g. <br>";
  2769. $rec .= "'DROP INDEX \"Report_Idx\"; CREATE INDEX \"Report_Idx\" ON history USING btree (\"timestamp\", reading)' <br>";
  2770. $rec .= "Depending on your database size this command may running a long time. <br>";
  2771. }
  2772. }
  2773. }
  2774. } else {
  2775. $check .= "You don't use any DbRep-device assigned to $name. Hence an index for DbRep isn't needed. <br>";
  2776. $rec = "settings o.k.";
  2777. }
  2778. $check .= "<b>Recommendation:</b> $rec <br><br>";
  2779. $check .= "</html>";
  2780. return $check;
  2781. }
  2782. sub DbLog_sqlget($$) {
  2783. my ($hash,$sql)= @_;
  2784. my $name = $hash->{NAME};
  2785. my ($dbh,$sth,@sr);
  2786. Log3 ($name, 4, "DbLog $name - Executing SQL: $sql");
  2787. $dbh = DbLog_ConnectNewDBH($hash);
  2788. return if(!$dbh);
  2789. eval { $sth = $dbh->prepare("$sql");
  2790. $sth->execute;
  2791. };
  2792. if($@) {
  2793. $dbh->disconnect if($dbh);
  2794. Log3 ($name, 2, "DbLog $name - $@");
  2795. return @sr;
  2796. }
  2797. @sr = $sth->fetchrow;
  2798. $sth->finish;
  2799. $dbh->disconnect;
  2800. no warnings 'uninitialized';
  2801. Log3 ($name, 4, "DbLog $name - SQL result: @sr");
  2802. use warnings;
  2803. return @sr;
  2804. }
  2805. #########################################################################################
  2806. #
  2807. # Addlog - einfügen des Readingwertes eines gegebenen Devices
  2808. #
  2809. #########################################################################################
  2810. sub DbLog_AddLog($$$) {
  2811. my ($hash,$devrdspec,$value)= @_;
  2812. my $name = $hash->{NAME};
  2813. my $async = AttrVal($name, "asyncMode", undef);
  2814. my $value_fn = AttrVal( $name, "valueFn", "" );
  2815. my $ce = AttrVal($name, "cacheEvents", 0);
  2816. my ($dev_type,$dev_name,$dev_reading,$read_val,$event,$ut);
  2817. my @row_array;
  2818. return if(IsDisabled($name) || !$hash->{HELPER}{COLSET} || $init_done != 1);
  2819. # Funktion aus Attr valueFn validieren
  2820. if( $value_fn =~ m/^\s*(\{.*\})\s*$/s ) {
  2821. $value_fn = $1;
  2822. } else {
  2823. $value_fn = '';
  2824. }
  2825. my $ts = TimeNow();
  2826. my $now = gettimeofday();
  2827. my $rdspec = (split ":",$devrdspec)[-1];
  2828. my @dc = split(":",$devrdspec);
  2829. pop @dc;
  2830. my $devspec = join(':',@dc);
  2831. my @exdvs = devspec2array($devspec);
  2832. foreach (@exdvs) {
  2833. $dev_name = $_;
  2834. if(!$defs{$dev_name}) {
  2835. Log3 $name, 2, "DbLog $name -> Device '$dev_name' used by addLog doesn't exist !";
  2836. next;
  2837. }
  2838. my $r = $defs{$dev_name}{READINGS};
  2839. my @exrds;
  2840. foreach my $rd (sort keys %{$r}) {
  2841. push @exrds,$rd if($rd =~ m/^$rdspec$/);
  2842. }
  2843. Log3 $name, 4, "DbLog $name -> Readings extracted from Regex: @exrds";
  2844. if(!@exrds) {
  2845. Log3 $name, 2, "DbLog $name -> no Reading of device '$dev_name' selected from '$rdspec' used by addLog !";
  2846. next;
  2847. }
  2848. foreach (@exrds) {
  2849. $dev_reading = $_;
  2850. $read_val = $value?$value:ReadingsVal($dev_name,$dev_reading,"");
  2851. $dev_type = uc($defs{$dev_name}{TYPE});
  2852. # dummy-Event zusammenstellen
  2853. $event = $dev_reading.": ".$read_val;
  2854. # den zusammengestellten Event parsen lassen (evtl. Unit zuweisen)
  2855. my @r = DbLog_ParseEvent($dev_name, $dev_type, $event);
  2856. $dev_reading = $r[0];
  2857. $read_val = $r[1];
  2858. $ut = $r[2];
  2859. if(!defined $dev_reading) {$dev_reading = "";}
  2860. if(!defined $read_val) {$read_val = "";}
  2861. if(!defined $ut || $ut eq "") {$ut = AttrVal("$dev_name", "unit", "");}
  2862. $event = "addLog";
  2863. $defs{$dev_name}{Helper}{DBLOG}{$dev_reading}{$hash->{NAME}}{TIME} = $now;
  2864. $defs{$dev_name}{Helper}{DBLOG}{$dev_reading}{$hash->{NAME}}{VALUE} = $read_val;
  2865. # Anwender spezifische Funktion anwenden
  2866. if($value_fn ne '') {
  2867. my $TIMESTAMP = $ts;
  2868. my $DEVICE = $dev_name;
  2869. my $DEVICETYPE = $dev_type;
  2870. my $EVENT = $event;
  2871. my $READING = $dev_reading;
  2872. my $VALUE = $read_val;
  2873. my $UNIT = $ut;
  2874. my $IGNORE = 0;
  2875. eval $value_fn;
  2876. Log3 $name, 2, "DbLog $name -> error valueFn: ".$@ if($@);
  2877. next if($IGNORE); # aktueller Event wird nicht geloggt wenn $IGNORE=1 gesetzt in $value_fn
  2878. $ts = $TIMESTAMP if($TIMESTAMP =~ /(19[0-9][0-9]|2[0-9][0-9][0-9])-(0[1-9]|1[1-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]) (0[0-9]|1[1-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])/);
  2879. $dev_name = $DEVICE if($DEVICE ne '');
  2880. $dev_type = $DEVICETYPE if($DEVICETYPE ne '');
  2881. $dev_reading = $READING if($READING ne '');
  2882. $read_val = $VALUE if(defined $VALUE);
  2883. $ut = $UNIT if(defined $UNIT);
  2884. }
  2885. # Daten auf maximale Länge beschneiden
  2886. ($dev_name,$dev_type,$event,$dev_reading,$read_val,$ut) = DbLog_cutCol($hash,$dev_name,$dev_type,$event,$dev_reading,$read_val,$ut);
  2887. my $row = ($ts."|".$dev_name."|".$dev_type."|".$event."|".$dev_reading."|".$read_val."|".$ut);
  2888. Log3 $hash->{NAME}, 3, "DbLog $name -> addLog created - TS: $ts, Device: $dev_name, Type: $dev_type, Event: $event, Reading: $dev_reading, Value: $read_val, Unit: $ut"
  2889. if(!AttrVal($name, "suppressAddLogV3",0));
  2890. if($async) {
  2891. # asynchoner non-blocking Mode
  2892. # Cache & CacheIndex für Events zum asynchronen Schreiben in DB
  2893. $hash->{cache}{index}++;
  2894. my $index = $hash->{cache}{index};
  2895. $hash->{cache}{memcache}{$index} = $row;
  2896. my $memcount = $hash->{cache}{memcache}?scalar(keys%{$hash->{cache}{memcache}}):0;
  2897. if($ce == 1) {
  2898. readingsSingleUpdate($hash, "CacheUsage", $memcount, 1);
  2899. } else {
  2900. readingsSingleUpdate($hash, 'CacheUsage', $memcount, 0);
  2901. }
  2902. } else {
  2903. # synchoner Mode
  2904. push(@row_array, $row);
  2905. }
  2906. }
  2907. }
  2908. if(!$async) {
  2909. if(@row_array) {
  2910. # synchoner Mode
  2911. # return wenn "reopen" mit Ablaufzeit gestartet ist
  2912. return if($hash->{HELPER}{REOPEN_RUNS});
  2913. my $error = DbLog_Push($hash, 1, @row_array);
  2914. my $state = $error?$error:(IsDisabled($name))?"disabled":"connected";
  2915. my $evt = ($state eq $hash->{HELPER}{OLDSTATE})?0:1;
  2916. readingsSingleUpdate($hash, "state", $state, $evt);
  2917. $hash->{HELPER}{OLDSTATE} = $state;
  2918. Log3 $name, 5, "DbLog $name -> DbLog_Push Returncode: $error";
  2919. }
  2920. }
  2921. return;
  2922. }
  2923. #########################################################################################
  2924. #
  2925. # Subroutine cutCol - Daten auf maximale Länge beschneiden
  2926. #
  2927. #########################################################################################
  2928. sub DbLog_cutCol($$$$$$$) {
  2929. my ($hash,$dn,$dt,$evt,$rd,$val,$unit)= @_;
  2930. my $name = $hash->{NAME};
  2931. my $colevent = AttrVal($name, 'colEvent', undef);
  2932. my $colreading = AttrVal($name, 'colReading', undef);
  2933. my $colvalue = AttrVal($name, 'colValue', undef);
  2934. if ($hash->{MODEL} ne 'SQLITE' || defined($colevent) || defined($colreading) || defined($colvalue) ) {
  2935. $dn = substr($dn,0, $hash->{HELPER}{DEVICECOL});
  2936. $dt = substr($dt,0, $hash->{HELPER}{TYPECOL});
  2937. $evt = substr($evt,0, $hash->{HELPER}{EVENTCOL});
  2938. $rd = substr($rd,0, $hash->{HELPER}{READINGCOL});
  2939. $val = substr($val,0, $hash->{HELPER}{VALUECOL});
  2940. $unit = substr($unit,0, $hash->{HELPER}{UNITCOL});
  2941. }
  2942. return ($dn,$dt,$evt,$rd,$val,$unit);
  2943. }
  2944. #########################################################################################
  2945. ### DBLog - Historische Werte ausduennen (alte blockiernde Variante) > Forum #41089
  2946. #########################################################################################
  2947. sub DbLog_reduceLog($@) {
  2948. my ($hash,@a) = @_;
  2949. my ($ret,$cmd,$row,$filter,$exclude,$c,$day,$hour,$lastHour,$updDate,$updHour,$average,$processingDay,$lastUpdH,%hourlyKnown,%averageHash,@excludeRegex,@dayRows,@averageUpd,@averageUpdD);
  2950. my ($name,$startTime,$currentHour,$currentDay,$deletedCount,$updateCount,$sum,$rowCount,$excludeCount) = ($hash->{NAME},time(),99,0,0,0,0,0,0);
  2951. my $dbh = DbLog_ConnectNewDBH($hash);
  2952. return if(!$dbh);
  2953. if ($a[-1] =~ /^EXCLUDE=(.+:.+)+/i) {
  2954. ($filter) = $a[-1] =~ /^EXCLUDE=(.+)/i;
  2955. @excludeRegex = split(',',$filter);
  2956. } elsif ($a[-1] =~ /^INCLUDE=.+:.+$/i) {
  2957. $filter = 1;
  2958. }
  2959. if (defined($a[3])) {
  2960. $average = ($a[3] =~ /average=day/i) ? "AVERAGE=DAY" : ($a[3] =~ /average/i) ? "AVERAGE=HOUR" : 0;
  2961. }
  2962. Log3($name, 3, "DbLog $name: reduceLog requested with DAYS=$a[2]"
  2963. .(($average || $filter) ? ', ' : '').(($average) ? "$average" : '')
  2964. .(($average && $filter) ? ", " : '').(($filter) ? uc((split('=',$a[-1]))[0]).'='.(split('=',$a[-1]))[1] : ''));
  2965. if ($hash->{MODEL} eq 'SQLITE') { $cmd = "datetime('now', '-$a[2] days')"; }
  2966. elsif ($hash->{MODEL} eq 'MYSQL') { $cmd = "DATE_SUB(CURDATE(),INTERVAL $a[2] DAY)"; }
  2967. elsif ($hash->{MODEL} eq 'POSTGRESQL') { $cmd = "NOW() - INTERVAL '$a[2]' DAY"; }
  2968. else { $ret = 'Unknown database type.'; }
  2969. if ($cmd) {
  2970. my $sth_del = $dbh->prepare_cached("DELETE FROM history WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?) AND (VALUE=?)");
  2971. my $sth_upd = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=?, EVENT=?, VALUE=? WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?) AND (VALUE=?)");
  2972. my $sth_delD = $dbh->prepare_cached("DELETE FROM history WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?)");
  2973. my $sth_updD = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=?, EVENT=?, VALUE=? WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?)");
  2974. my $sth_get = $dbh->prepare("SELECT TIMESTAMP,DEVICE,'',READING,VALUE FROM history WHERE "
  2975. .($a[-1] =~ /^INCLUDE=(.+):(.+)$/i ? "DEVICE like '$1' AND READING like '$2' AND " : '')
  2976. ."TIMESTAMP < $cmd ORDER BY TIMESTAMP ASC"); # '' was EVENT, no longer in use
  2977. $sth_get->execute();
  2978. do {
  2979. $row = $sth_get->fetchrow_arrayref || ['0000-00-00 00:00:00','D','','R','V']; # || execute last-day dummy
  2980. $ret = 1;
  2981. ($day,$hour) = $row->[0] =~ /-(\d{2})\s(\d{2}):/;
  2982. $rowCount++ if($day != 00);
  2983. if ($day != $currentDay) {
  2984. if ($currentDay) { # false on first executed day
  2985. if (scalar @dayRows) {
  2986. ($lastHour) = $dayRows[-1]->[0] =~ /(.*\d+\s\d{2}):/;
  2987. $c = 0;
  2988. for my $delRow (@dayRows) {
  2989. $c++ if($day != 00 || $delRow->[0] !~ /$lastHour/);
  2990. }
  2991. if($c) {
  2992. $deletedCount += $c;
  2993. Log3($name, 3, "DbLog $name: reduceLog deleting $c records of day: $processingDay");
  2994. $dbh->{RaiseError} = 1;
  2995. $dbh->{PrintError} = 0;
  2996. $dbh->begin_work();
  2997. eval {
  2998. for my $delRow (@dayRows) {
  2999. if($day != 00 || $delRow->[0] !~ /$lastHour/) {
  3000. Log3($name, 5, "DbLog $name: DELETE FROM history WHERE (DEVICE=$delRow->[1]) AND (READING=$delRow->[3]) AND (TIMESTAMP=$delRow->[0]) AND (VALUE=$delRow->[4])");
  3001. $sth_del->execute(($delRow->[1], $delRow->[3], $delRow->[0], $delRow->[4]));
  3002. }
  3003. }
  3004. };
  3005. if ($@) {
  3006. Log3($hash->{NAME}, 3, "DbLog $name: reduceLog ! FAILED ! for day $processingDay");
  3007. $dbh->rollback();
  3008. $ret = 0;
  3009. } else {
  3010. $dbh->commit();
  3011. }
  3012. $dbh->{RaiseError} = 0;
  3013. $dbh->{PrintError} = 1;
  3014. }
  3015. @dayRows = ();
  3016. }
  3017. if ($ret && defined($a[3]) && $a[3] =~ /average/i) {
  3018. $dbh->{RaiseError} = 1;
  3019. $dbh->{PrintError} = 0;
  3020. $dbh->begin_work();
  3021. eval {
  3022. push(@averageUpd, {%hourlyKnown}) if($day != 00);
  3023. $c = 0;
  3024. for my $hourHash (@averageUpd) { # Only count for logging...
  3025. for my $hourKey (keys %$hourHash) {
  3026. $c++ if ($hourHash->{$hourKey}->[0] && scalar(@{$hourHash->{$hourKey}->[4]}) > 1);
  3027. }
  3028. }
  3029. $updateCount += $c;
  3030. Log3($name, 3, "DbLog $name: reduceLog (hourly-average) updating $c records of day: $processingDay") if($c); # else only push to @averageUpdD
  3031. for my $hourHash (@averageUpd) {
  3032. for my $hourKey (keys %$hourHash) {
  3033. if ($hourHash->{$hourKey}->[0]) { # true if reading is a number
  3034. ($updDate,$updHour) = $hourHash->{$hourKey}->[0] =~ /(.*\d+)\s(\d{2}):/;
  3035. if (scalar(@{$hourHash->{$hourKey}->[4]}) > 1) { # true if reading has multiple records this hour
  3036. for (@{$hourHash->{$hourKey}->[4]}) { $sum += $_; }
  3037. $average = sprintf('%.3f', $sum/scalar(@{$hourHash->{$hourKey}->[4]}) );
  3038. $sum = 0;
  3039. Log3($name, 5, "DbLog $name: UPDATE history SET TIMESTAMP=$updDate $updHour:30:00, EVENT='rl_av_h', VALUE=$average WHERE DEVICE=$hourHash->{$hourKey}->[1] AND READING=$hourHash->{$hourKey}->[3] AND TIMESTAMP=$hourHash->{$hourKey}->[0] AND VALUE=$hourHash->{$hourKey}->[4]->[0]");
  3040. $sth_upd->execute(("$updDate $updHour:30:00", 'rl_av_h', $average, $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $hourHash->{$hourKey}->[0], $hourHash->{$hourKey}->[4]->[0]));
  3041. push(@averageUpdD, ["$updDate $updHour:30:00", 'rl_av_h', $average, $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $updDate]) if (defined($a[3]) && $a[3] =~ /average=day/i);
  3042. } else {
  3043. push(@averageUpdD, [$hourHash->{$hourKey}->[0], $hourHash->{$hourKey}->[2], $hourHash->{$hourKey}->[4]->[0], $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $updDate]) if (defined($a[3]) && $a[3] =~ /average=day/i);
  3044. }
  3045. }
  3046. }
  3047. }
  3048. };
  3049. if ($@) {
  3050. Log3($hash->{NAME}, 3, "DbLog $name: reduceLog average=hour ! FAILED ! for day $processingDay");
  3051. $dbh->rollback();
  3052. @averageUpdD = ();
  3053. } else {
  3054. $dbh->commit();
  3055. }
  3056. $dbh->{RaiseError} = 0;
  3057. $dbh->{PrintError} = 1;
  3058. @averageUpd = ();
  3059. }
  3060. if (defined($a[3]) && $a[3] =~ /average=day/i && scalar(@averageUpdD) && $day != 00) {
  3061. $dbh->{RaiseError} = 1;
  3062. $dbh->{PrintError} = 0;
  3063. $dbh->begin_work();
  3064. eval {
  3065. for (@averageUpdD) {
  3066. push(@{$averageHash{$_->[3].$_->[4]}->{tedr}}, [$_->[0], $_->[1], $_->[3], $_->[4]]);
  3067. $averageHash{$_->[3].$_->[4]}->{sum} += $_->[2];
  3068. $averageHash{$_->[3].$_->[4]}->{date} = $_->[5];
  3069. }
  3070. $c = 0;
  3071. for (keys %averageHash) {
  3072. if(scalar @{$averageHash{$_}->{tedr}} == 1) {
  3073. delete $averageHash{$_};
  3074. } else {
  3075. $c += (scalar(@{$averageHash{$_}->{tedr}}) - 1);
  3076. }
  3077. }
  3078. $deletedCount += $c;
  3079. $updateCount += keys(%averageHash);
  3080. Log3($name, 3, "DbLog $name: reduceLog (daily-average) updating ".(keys %averageHash).", deleting $c records of day: $processingDay") if(keys %averageHash);
  3081. for my $reading (keys %averageHash) {
  3082. $average = sprintf('%.3f', $averageHash{$reading}->{sum}/scalar(@{$averageHash{$reading}->{tedr}}));
  3083. $lastUpdH = pop @{$averageHash{$reading}->{tedr}};
  3084. for (@{$averageHash{$reading}->{tedr}}) {
  3085. Log3($name, 5, "DbLog $name: DELETE FROM history WHERE DEVICE='$_->[2]' AND READING='$_->[3]' AND TIMESTAMP='$_->[0]'");
  3086. $sth_delD->execute(($_->[2], $_->[3], $_->[0]));
  3087. }
  3088. Log3($name, 5, "DbLog $name: UPDATE history SET TIMESTAMP=$averageHash{$reading}->{date} 12:00:00, EVENT='rl_av_d', VALUE=$average WHERE (DEVICE=$lastUpdH->[2]) AND (READING=$lastUpdH->[3]) AND (TIMESTAMP=$lastUpdH->[0])");
  3089. $sth_updD->execute(($averageHash{$reading}->{date}." 12:00:00", 'rl_av_d', $average, $lastUpdH->[2], $lastUpdH->[3], $lastUpdH->[0]));
  3090. }
  3091. };
  3092. if ($@) {
  3093. Log3($hash->{NAME}, 3, "DbLog $name: reduceLog average=day ! FAILED ! for day $processingDay");
  3094. $dbh->rollback();
  3095. } else {
  3096. $dbh->commit();
  3097. }
  3098. $dbh->{RaiseError} = 0;
  3099. $dbh->{PrintError} = 1;
  3100. }
  3101. %averageHash = ();
  3102. %hourlyKnown = ();
  3103. @averageUpd = ();
  3104. @averageUpdD = ();
  3105. $currentHour = 99;
  3106. }
  3107. $currentDay = $day;
  3108. }
  3109. if ($hour != $currentHour) { # forget records from last hour, but remember these for average
  3110. if (defined($a[3]) && $a[3] =~ /average/i && keys(%hourlyKnown)) {
  3111. push(@averageUpd, {%hourlyKnown});
  3112. }
  3113. %hourlyKnown = ();
  3114. $currentHour = $hour;
  3115. }
  3116. if (defined $hourlyKnown{$row->[1].$row->[3]}) { # remember first readings for device per h, other can be deleted
  3117. push(@dayRows, [@$row]);
  3118. if (defined($a[3]) && $a[3] =~ /average/i && defined($row->[4]) && $row->[4] =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ && $hourlyKnown{$row->[1].$row->[3]}->[0]) {
  3119. if ($hourlyKnown{$row->[1].$row->[3]}->[0]) {
  3120. push(@{$hourlyKnown{$row->[1].$row->[3]}->[4]}, $row->[4]);
  3121. }
  3122. }
  3123. } else {
  3124. $exclude = 0;
  3125. for (@excludeRegex) {
  3126. $exclude = 1 if("$row->[1]:$row->[3]" =~ /^$_$/);
  3127. }
  3128. if ($exclude) {
  3129. $excludeCount++ if($day != 00);
  3130. } else {
  3131. $hourlyKnown{$row->[1].$row->[3]} = (defined($row->[4]) && $row->[4] =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) ? [$row->[0],$row->[1],$row->[2],$row->[3],[$row->[4]]] : [0];
  3132. }
  3133. }
  3134. $processingDay = (split(' ',$row->[0]))[0];
  3135. } while( $day != 00 );
  3136. my $result = "Rows processed: $rowCount, deleted: $deletedCount"
  3137. .((defined($a[3]) && $a[3] =~ /average/i)? ", updated: $updateCount" : '')
  3138. .(($excludeCount)? ", excluded: $excludeCount" : '')
  3139. .", time: ".sprintf('%.2f',time() - $startTime)."sec";
  3140. Log3($name, 3, "DbLog $name: reduceLog executed. $result");
  3141. readingsSingleUpdate($hash,"reduceLogState",$result,1);
  3142. $ret = "reduceLog executed. $result";
  3143. }
  3144. $dbh->disconnect();
  3145. return $ret;
  3146. }
  3147. #########################################################################################
  3148. ### DBLog - Historische Werte ausduennen non-blocking > Forum #41089
  3149. #########################################################################################
  3150. sub DbLog_reduceLogNbl($) {
  3151. my ($name) = @_;
  3152. my $hash = $defs{$name};
  3153. my $dbconn = $hash->{dbconn};
  3154. my $dbuser = $hash->{dbuser};
  3155. my $dbpassword = $attr{"sec$name"}{secret};
  3156. my @a = @{$hash->{HELPER}{REDUCELOG}};
  3157. delete $hash->{HELPER}{REDUCELOG};
  3158. my ($ret,$cmd,$row,$filter,$exclude,$c,$day,$hour,$lastHour,$updDate,$updHour,$average,$processingDay,$lastUpdH,%hourlyKnown,%averageHash,@excludeRegex,@dayRows,@averageUpd,@averageUpdD);
  3159. my ($startTime,$currentHour,$currentDay,$deletedCount,$updateCount,$sum,$rowCount,$excludeCount) = (time(),99,0,0,0,0,0,0);
  3160. my $dbh;
  3161. Log3 ($name, 5, "DbLog $name -> Start DbLog_reduceLogNbl");
  3162. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1 }); };
  3163. if ($@) {
  3164. my $err = encode_base64($@,"");
  3165. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3166. Log3 ($name, 5, "DbLog $name -> DbLog_reduceLogNbl finished");
  3167. return "$name|''|$err";
  3168. }
  3169. if ($a[-1] =~ /^EXCLUDE=(.+:.+)+/i) {
  3170. ($filter) = $a[-1] =~ /^EXCLUDE=(.+)/i;
  3171. @excludeRegex = split(',',$filter);
  3172. } elsif ($a[-1] =~ /^INCLUDE=.+:.+$/i) {
  3173. $filter = 1;
  3174. }
  3175. if (defined($a[3])) {
  3176. $average = ($a[3] =~ /average=day/i) ? "AVERAGE=DAY" : ($a[3] =~ /average/i) ? "AVERAGE=HOUR" : 0;
  3177. }
  3178. Log3($name, 3, "DbLog $name: reduceLogNbl requested with DAYS=$a[2]"
  3179. .(($average || $filter) ? ', ' : '').(($average) ? "$average" : '')
  3180. .(($average && $filter) ? ", " : '').(($filter) ? uc((split('=',$a[-1]))[0]).'='.(split('=',$a[-1]))[1] : ''));
  3181. if ($hash->{MODEL} eq 'SQLITE') { $cmd = "datetime('now', '-$a[2] days')"; }
  3182. elsif ($hash->{MODEL} eq 'MYSQL') { $cmd = "DATE_SUB(CURDATE(),INTERVAL $a[2] DAY)"; }
  3183. elsif ($hash->{MODEL} eq 'POSTGRESQL') { $cmd = "NOW() - INTERVAL '$a[2]' DAY"; }
  3184. else { $ret = 'Unknown database type.'; }
  3185. if ($cmd) {
  3186. my ($sth_del, $sth_upd, $sth_delD, $sth_updD, $sth_get);
  3187. eval { $sth_del = $dbh->prepare_cached("DELETE FROM history WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?) AND (VALUE=?)");
  3188. $sth_upd = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=?, EVENT=?, VALUE=? WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?) AND (VALUE=?)");
  3189. $sth_delD = $dbh->prepare_cached("DELETE FROM history WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?)");
  3190. $sth_updD = $dbh->prepare_cached("UPDATE history SET TIMESTAMP=?, EVENT=?, VALUE=? WHERE (DEVICE=?) AND (READING=?) AND (TIMESTAMP=?)");
  3191. $sth_get = $dbh->prepare("SELECT TIMESTAMP,DEVICE,'',READING,VALUE FROM history WHERE "
  3192. .($a[-1] =~ /^INCLUDE=(.+):(.+)$/i ? "DEVICE like '$1' AND READING like '$2' AND " : '')
  3193. ."TIMESTAMP < $cmd ORDER BY TIMESTAMP ASC"); # '' was EVENT, no longer in use
  3194. };
  3195. if ($@) {
  3196. my $err = encode_base64($@,"");
  3197. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3198. Log3 ($name, 5, "DbLog $name -> DbLog_reduceLogNbl finished");
  3199. return "$name|''|$err";
  3200. }
  3201. eval { $sth_get->execute(); };
  3202. if ($@) {
  3203. my $err = encode_base64($@,"");
  3204. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3205. Log3 ($name, 5, "DbLog $name -> DbLog_reduceLogNbl finished");
  3206. return "$name|''|$err";
  3207. }
  3208. do {
  3209. $row = $sth_get->fetchrow_arrayref || ['0000-00-00 00:00:00','D','','R','V']; # || execute last-day dummy
  3210. $ret = 1;
  3211. ($day,$hour) = $row->[0] =~ /-(\d{2})\s(\d{2}):/;
  3212. $rowCount++ if($day != 00);
  3213. if ($day != $currentDay) {
  3214. if ($currentDay) { # false on first executed day
  3215. if (scalar @dayRows) {
  3216. ($lastHour) = $dayRows[-1]->[0] =~ /(.*\d+\s\d{2}):/;
  3217. $c = 0;
  3218. for my $delRow (@dayRows) {
  3219. $c++ if($day != 00 || $delRow->[0] !~ /$lastHour/);
  3220. }
  3221. if($c) {
  3222. $deletedCount += $c;
  3223. Log3($name, 3, "DbLog $name: reduceLogNbl deleting $c records of day: $processingDay");
  3224. $dbh->{RaiseError} = 1;
  3225. $dbh->{PrintError} = 0;
  3226. eval { $dbh->begin_work(); };
  3227. if ($@) {
  3228. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3229. }
  3230. eval {
  3231. for my $delRow (@dayRows) {
  3232. if($day != 00 || $delRow->[0] !~ /$lastHour/) {
  3233. Log3($name, 5, "DbLog $name: DELETE FROM history WHERE (DEVICE=$delRow->[1]) AND (READING=$delRow->[3]) AND (TIMESTAMP=$delRow->[0]) AND (VALUE=$delRow->[4])");
  3234. $sth_del->execute(($delRow->[1], $delRow->[3], $delRow->[0], $delRow->[4]));
  3235. }
  3236. }
  3237. };
  3238. if ($@) {
  3239. Log3($hash->{NAME}, 3, "DbLog $name: reduceLogNbl ! FAILED ! for day $processingDay");
  3240. eval {$dbh->rollback() if(!$dbh->{AutoCommit});};
  3241. if ($@) {
  3242. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3243. }
  3244. $ret = 0;
  3245. } else {
  3246. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  3247. if ($@) {
  3248. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3249. }
  3250. }
  3251. $dbh->{RaiseError} = 0;
  3252. $dbh->{PrintError} = 1;
  3253. }
  3254. @dayRows = ();
  3255. }
  3256. if ($ret && defined($a[3]) && $a[3] =~ /average/i) {
  3257. $dbh->{RaiseError} = 1;
  3258. $dbh->{PrintError} = 0;
  3259. eval { $dbh->begin_work(); };
  3260. if ($@) {
  3261. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3262. }
  3263. eval {
  3264. push(@averageUpd, {%hourlyKnown}) if($day != 00);
  3265. $c = 0;
  3266. for my $hourHash (@averageUpd) { # Only count for logging...
  3267. for my $hourKey (keys %$hourHash) {
  3268. $c++ if ($hourHash->{$hourKey}->[0] && scalar(@{$hourHash->{$hourKey}->[4]}) > 1);
  3269. }
  3270. }
  3271. $updateCount += $c;
  3272. Log3($name, 3, "DbLog $name: reduceLogNbl (hourly-average) updating $c records of day: $processingDay") if($c); # else only push to @averageUpdD
  3273. for my $hourHash (@averageUpd) {
  3274. for my $hourKey (keys %$hourHash) {
  3275. if ($hourHash->{$hourKey}->[0]) { # true if reading is a number
  3276. ($updDate,$updHour) = $hourHash->{$hourKey}->[0] =~ /(.*\d+)\s(\d{2}):/;
  3277. if (scalar(@{$hourHash->{$hourKey}->[4]}) > 1) { # true if reading has multiple records this hour
  3278. for (@{$hourHash->{$hourKey}->[4]}) { $sum += $_; }
  3279. $average = sprintf('%.3f', $sum/scalar(@{$hourHash->{$hourKey}->[4]}) );
  3280. $sum = 0;
  3281. Log3($name, 5, "DbLog $name: UPDATE history SET TIMESTAMP=$updDate $updHour:30:00, EVENT='rl_av_h', VALUE=$average WHERE DEVICE=$hourHash->{$hourKey}->[1] AND READING=$hourHash->{$hourKey}->[3] AND TIMESTAMP=$hourHash->{$hourKey}->[0] AND VALUE=$hourHash->{$hourKey}->[4]->[0]");
  3282. $sth_upd->execute(("$updDate $updHour:30:00", 'rl_av_h', $average, $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $hourHash->{$hourKey}->[0], $hourHash->{$hourKey}->[4]->[0]));
  3283. push(@averageUpdD, ["$updDate $updHour:30:00", 'rl_av_h', $average, $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $updDate]) if (defined($a[3]) && $a[3] =~ /average=day/i);
  3284. } else {
  3285. push(@averageUpdD, [$hourHash->{$hourKey}->[0], $hourHash->{$hourKey}->[2], $hourHash->{$hourKey}->[4]->[0], $hourHash->{$hourKey}->[1], $hourHash->{$hourKey}->[3], $updDate]) if (defined($a[3]) && $a[3] =~ /average=day/i);
  3286. }
  3287. }
  3288. }
  3289. }
  3290. };
  3291. if ($@) {
  3292. Log3($hash->{NAME}, 3, "DbLog $name: reduceLogNbl average=hour ! FAILED ! for day $processingDay");
  3293. eval {$dbh->rollback() if(!$dbh->{AutoCommit});};
  3294. if ($@) {
  3295. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3296. }
  3297. @averageUpdD = ();
  3298. } else {
  3299. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  3300. if ($@) {
  3301. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3302. }
  3303. }
  3304. $dbh->{RaiseError} = 0;
  3305. $dbh->{PrintError} = 1;
  3306. @averageUpd = ();
  3307. }
  3308. if (defined($a[3]) && $a[3] =~ /average=day/i && scalar(@averageUpdD) && $day != 00) {
  3309. $dbh->{RaiseError} = 1;
  3310. $dbh->{PrintError} = 0;
  3311. eval { $dbh->begin_work(); };
  3312. if ($@) {
  3313. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3314. }
  3315. eval {
  3316. for (@averageUpdD) {
  3317. push(@{$averageHash{$_->[3].$_->[4]}->{tedr}}, [$_->[0], $_->[1], $_->[3], $_->[4]]);
  3318. $averageHash{$_->[3].$_->[4]}->{sum} += $_->[2];
  3319. $averageHash{$_->[3].$_->[4]}->{date} = $_->[5];
  3320. }
  3321. $c = 0;
  3322. for (keys %averageHash) {
  3323. if(scalar @{$averageHash{$_}->{tedr}} == 1) {
  3324. delete $averageHash{$_};
  3325. } else {
  3326. $c += (scalar(@{$averageHash{$_}->{tedr}}) - 1);
  3327. }
  3328. }
  3329. $deletedCount += $c;
  3330. $updateCount += keys(%averageHash);
  3331. Log3($name, 3, "DbLog $name: reduceLogNbl (daily-average) updating ".(keys %averageHash).", deleting $c records of day: $processingDay") if(keys %averageHash);
  3332. for my $reading (keys %averageHash) {
  3333. $average = sprintf('%.3f', $averageHash{$reading}->{sum}/scalar(@{$averageHash{$reading}->{tedr}}));
  3334. $lastUpdH = pop @{$averageHash{$reading}->{tedr}};
  3335. for (@{$averageHash{$reading}->{tedr}}) {
  3336. Log3($name, 5, "DbLog $name: DELETE FROM history WHERE DEVICE='$_->[2]' AND READING='$_->[3]' AND TIMESTAMP='$_->[0]'");
  3337. $sth_delD->execute(($_->[2], $_->[3], $_->[0]));
  3338. }
  3339. Log3($name, 5, "DbLog $name: UPDATE history SET TIMESTAMP=$averageHash{$reading}->{date} 12:00:00, EVENT='rl_av_d', VALUE=$average WHERE (DEVICE=$lastUpdH->[2]) AND (READING=$lastUpdH->[3]) AND (TIMESTAMP=$lastUpdH->[0])");
  3340. $sth_updD->execute(($averageHash{$reading}->{date}." 12:00:00", 'rl_av_d', $average, $lastUpdH->[2], $lastUpdH->[3], $lastUpdH->[0]));
  3341. }
  3342. };
  3343. if ($@) {
  3344. Log3($hash->{NAME}, 3, "DbLog $name: reduceLogNbl average=day ! FAILED ! for day $processingDay");
  3345. eval {$dbh->rollback() if(!$dbh->{AutoCommit});};
  3346. if ($@) {
  3347. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3348. }
  3349. } else {
  3350. eval {$dbh->commit() if(!$dbh->{AutoCommit});};
  3351. if ($@) {
  3352. Log3 ($name, 2, "DbLog $name -> DbLog_reduceLogNbl - $@");
  3353. }
  3354. }
  3355. $dbh->{RaiseError} = 0;
  3356. $dbh->{PrintError} = 1;
  3357. }
  3358. %averageHash = ();
  3359. %hourlyKnown = ();
  3360. @averageUpd = ();
  3361. @averageUpdD = ();
  3362. $currentHour = 99;
  3363. }
  3364. $currentDay = $day;
  3365. }
  3366. if ($hour != $currentHour) { # forget records from last hour, but remember these for average
  3367. if (defined($a[3]) && $a[3] =~ /average/i && keys(%hourlyKnown)) {
  3368. push(@averageUpd, {%hourlyKnown});
  3369. }
  3370. %hourlyKnown = ();
  3371. $currentHour = $hour;
  3372. }
  3373. if (defined $hourlyKnown{$row->[1].$row->[3]}) { # remember first readings for device per h, other can be deleted
  3374. push(@dayRows, [@$row]);
  3375. if (defined($a[3]) && $a[3] =~ /average/i && defined($row->[4]) && $row->[4] =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/ && $hourlyKnown{$row->[1].$row->[3]}->[0]) {
  3376. if ($hourlyKnown{$row->[1].$row->[3]}->[0]) {
  3377. push(@{$hourlyKnown{$row->[1].$row->[3]}->[4]}, $row->[4]);
  3378. }
  3379. }
  3380. } else {
  3381. $exclude = 0;
  3382. for (@excludeRegex) {
  3383. $exclude = 1 if("$row->[1]:$row->[3]" =~ /^$_$/);
  3384. }
  3385. if ($exclude) {
  3386. $excludeCount++ if($day != 00);
  3387. } else {
  3388. $hourlyKnown{$row->[1].$row->[3]} = (defined($row->[4]) && $row->[4] =~ /^-?(?:\d+(?:\.\d*)?|\.\d+)$/) ? [$row->[0],$row->[1],$row->[2],$row->[3],[$row->[4]]] : [0];
  3389. }
  3390. }
  3391. $processingDay = (split(' ',$row->[0]))[0];
  3392. } while( $day != 00 );
  3393. my $result = "Rows processed: $rowCount, deleted: $deletedCount"
  3394. .((defined($a[3]) && $a[3] =~ /average/i)? ", updated: $updateCount" : '')
  3395. .(($excludeCount)? ", excluded: $excludeCount" : '')
  3396. .", time: ".sprintf('%.2f',time() - $startTime)."sec";
  3397. Log3($name, 3, "DbLog $name: reduceLogNbl finished. $result");
  3398. $ret = $result;
  3399. $ret = "reduceLogNbl finished. $result";
  3400. }
  3401. $dbh->disconnect();
  3402. $ret = encode_base64($ret,"");
  3403. Log3 ($name, 5, "DbLog $name -> DbLog_reduceLogNbl finished");
  3404. return "$name|$ret|0";
  3405. }
  3406. #########################################################################################
  3407. # DBLog - reduceLogNbl non-blocking Rückkehrfunktion
  3408. #########################################################################################
  3409. sub DbLog_reduceLogNbl_finished($) {
  3410. my ($string) = @_;
  3411. my @a = split("\\|",$string);
  3412. my $name = $a[0];
  3413. my $hash = $defs{$name};
  3414. my $ret = decode_base64($a[1]);
  3415. my $err = decode_base64($a[2]) if ($a[2]);
  3416. readingsSingleUpdate($hash,"reduceLogState",$err?$err:$ret,1);
  3417. delete $hash->{HELPER}{REDUCELOG_PID};
  3418. return;
  3419. }
  3420. #########################################################################################
  3421. # DBLog - count non-blocking
  3422. #########################################################################################
  3423. sub DbLog_countNbl($) {
  3424. my ($name) = @_;
  3425. my $hash = $defs{$name};
  3426. my ($cc,$hc,$bst,$st,$rt);
  3427. # Background-Startzeit
  3428. $bst = [gettimeofday];
  3429. my $dbh = DbLog_ConnectNewDBH($hash);
  3430. if (!$dbh) {
  3431. my $err = encode_base64("DbLog $name: DBLog_Set - count - DB connect not possible","");
  3432. return "$name|0|0|$err|0";
  3433. } else {
  3434. Log3 $name,4,"DbLog $name: Records count requested.";
  3435. # SQL-Startzeit
  3436. $st = [gettimeofday];
  3437. $hc = $dbh->selectrow_array('SELECT count(*) FROM history');
  3438. $cc = $dbh->selectrow_array('SELECT count(*) FROM current');
  3439. $dbh->disconnect();
  3440. # SQL-Laufzeit ermitteln
  3441. $rt = tv_interval($st);
  3442. }
  3443. # Background-Laufzeit ermitteln
  3444. my $brt = tv_interval($bst);
  3445. $rt = $rt.",".$brt;
  3446. return "$name|$cc|$hc|0|$rt";
  3447. }
  3448. #########################################################################################
  3449. # DBLog - count non-blocking Rückkehrfunktion
  3450. #########################################################################################
  3451. sub DbLog_countNbl_finished($)
  3452. {
  3453. my ($string) = @_;
  3454. my @a = split("\\|",$string);
  3455. my $name = $a[0];
  3456. my $hash = $defs{$name};
  3457. my $cc = $a[1];
  3458. my $hc = $a[2];
  3459. my $err = decode_base64($a[3]) if ($a[3]);
  3460. my $bt = $a[4] if($a[4]);
  3461. readingsSingleUpdate($hash,"state",$err,1) if($err);
  3462. readingsSingleUpdate($hash,"countHistory",$hc,1) if ($hc);
  3463. readingsSingleUpdate($hash,"countCurrent",$cc,1) if ($cc);
  3464. if(AttrVal($name, "showproctime", undef) && $bt) {
  3465. my ($rt,$brt) = split(",", $bt);
  3466. readingsBeginUpdate($hash);
  3467. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt));
  3468. readingsBulkUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt));
  3469. readingsEndUpdate($hash, 1);
  3470. }
  3471. delete $hash->{HELPER}{COUNT_PID};
  3472. return;
  3473. }
  3474. #########################################################################################
  3475. # DBLog - deleteOldDays non-blocking
  3476. #########################################################################################
  3477. sub DbLog_deldaysNbl($) {
  3478. my ($name) = @_;
  3479. my $hash = $defs{$name};
  3480. my $dbconn = $hash->{dbconn};
  3481. my $dbuser = $hash->{dbuser};
  3482. my $dbpassword = $attr{"sec$name"}{secret};
  3483. my $days = delete($hash->{HELPER}{DELDAYS});
  3484. my ($cmd,$dbh,$rows,$error,$sth,$ret,$bst,$brt,$st,$rt);
  3485. Log3 ($name, 5, "DbLog $name -> Start DbLog_deldaysNbl $days");
  3486. # Background-Startzeit
  3487. $bst = [gettimeofday];
  3488. eval {$dbh = DBI->connect("dbi:$dbconn", $dbuser, $dbpassword, { PrintError => 0, RaiseError => 1 });};
  3489. if ($@) {
  3490. $error = encode_base64($@,"");
  3491. Log3 ($name, 2, "DbLog $name - Error: $@");
  3492. Log3 ($name, 5, "DbLog $name -> DbLog_deldaysNbl finished");
  3493. return "$name|0|0|$error";
  3494. }
  3495. $cmd = "delete from history where TIMESTAMP < ";
  3496. if ($hash->{MODEL} eq 'SQLITE') {
  3497. $cmd .= "datetime('now', '-$days days')";
  3498. } elsif ($hash->{MODEL} eq 'MYSQL') {
  3499. $cmd .= "DATE_SUB(CURDATE(),INTERVAL $days DAY)";
  3500. } elsif ($hash->{MODEL} eq 'POSTGRESQL') {
  3501. $cmd .= "NOW() - INTERVAL '$days' DAY";
  3502. } else {
  3503. $ret = 'Unknown database type. Maybe you can try userCommand anyway.';
  3504. $error = encode_base64($ret,"");
  3505. Log3 ($name, 2, "DbLog $name - Error: $ret");
  3506. Log3 ($name, 5, "DbLog $name -> DbLog_deldaysNbl finished");
  3507. return "$name|0|0|$error";
  3508. }
  3509. # SQL-Startzeit
  3510. $st = [gettimeofday];
  3511. eval {
  3512. $sth = $dbh->prepare($cmd);
  3513. $sth->execute();
  3514. };
  3515. if ($@) {
  3516. $error = encode_base64($@,"");
  3517. Log3 ($name, 2, "DbLog $name - $@");
  3518. $dbh->disconnect;
  3519. Log3 ($name, 4, "DbLog $name -> BlockingCall DbLog_deldaysNbl finished");
  3520. return "$name|0|0|$error";
  3521. } else {
  3522. $rows = $sth->rows;
  3523. $dbh->commit() if(!$dbh->{AutoCommit});
  3524. $dbh->disconnect;
  3525. }
  3526. # SQL-Laufzeit ermitteln
  3527. $rt = tv_interval($st);
  3528. # Background-Laufzeit ermitteln
  3529. $brt = tv_interval($bst);
  3530. $rt = $rt.",".$brt;
  3531. Log3 ($name, 5, "DbLog $name -> DbLog_deldaysNbl finished");
  3532. return "$name|$rows|$rt|0";
  3533. }
  3534. #########################################################################################
  3535. # DBLog - deleteOldDays non-blocking Rückkehrfunktion
  3536. #########################################################################################
  3537. sub DbLog_deldaysNbl_done($) {
  3538. my ($string) = @_;
  3539. my @a = split("\\|",$string);
  3540. my $name = $a[0];
  3541. my $hash = $defs{$name};
  3542. my $rows = $a[1];
  3543. my $bt = $a[2] if($a[2]);
  3544. my $err = decode_base64($a[3]) if ($a[3]);
  3545. Log3 ($name, 5, "DbLog $name -> Start DbLog_deldaysNbl_done");
  3546. if ($err) {
  3547. readingsSingleUpdate($hash,"state",$err,1);
  3548. delete $hash->{HELPER}{DELDAYS_PID};
  3549. Log3 ($name, 5, "DbLog $name -> DbLog_deldaysNbl_done finished");
  3550. return;
  3551. } else {
  3552. if(AttrVal($name, "showproctime", undef) && $bt) {
  3553. my ($rt,$brt) = split(",", $bt);
  3554. readingsBeginUpdate($hash);
  3555. readingsBulkUpdate($hash, "background_processing_time", sprintf("%.4f",$brt));
  3556. readingsBulkUpdate($hash, "sql_processing_time", sprintf("%.4f",$rt));
  3557. readingsEndUpdate($hash, 1);
  3558. }
  3559. readingsSingleUpdate($hash, "lastRowsDeleted", $rows ,1);
  3560. }
  3561. my $db = (split(/;|=/, $hash->{dbconn}))[1];
  3562. Log3 ($name, 3, "DbLog $name -> deleteOldDaysNbl finished. $rows entries of database $db deleted.");
  3563. delete $hash->{HELPER}{DELDAYS_PID};
  3564. Log3 ($name, 5, "DbLog $name -> DbLog_deldaysNbl_done finished");
  3565. return;
  3566. }
  3567. ################################################################
  3568. #
  3569. # Charting Specific functions start here
  3570. #
  3571. ################################################################
  3572. ################################################################
  3573. #
  3574. # Error handling, returns a JSON String
  3575. #
  3576. ################################################################
  3577. sub jsonError($) {
  3578. my $errormsg = $_[0];
  3579. my $json = '{"success": "false", "msg":"'.$errormsg.'"}';
  3580. return $json;
  3581. }
  3582. ################################################################
  3583. #
  3584. # Prepare the SQL String
  3585. #
  3586. ################################################################
  3587. sub prepareSql(@) {
  3588. my ($hash, @a) = @_;
  3589. my $starttime = $_[5];
  3590. $starttime =~ s/_/ /;
  3591. my $endtime = $_[6];
  3592. $endtime =~ s/_/ /;
  3593. my $device = $_[7];
  3594. my $userquery = $_[8];
  3595. my $xaxis = $_[9];
  3596. my $yaxis = $_[10];
  3597. my $savename = $_[11];
  3598. my $jsonChartConfig = $_[12];
  3599. my $pagingstart = $_[13];
  3600. my $paginglimit = $_[14];
  3601. my $dbmodel = $hash->{MODEL};
  3602. my ($sql, $jsonstring, $countsql, $hourstats, $daystats, $weekstats, $monthstats, $yearstats);
  3603. if ($dbmodel eq "POSTGRESQL") {
  3604. ### POSTGRESQL Queries for Statistics ###
  3605. ### hour:
  3606. $hourstats = "SELECT to_char(timestamp, 'YYYY-MM-DD HH24:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
  3607. $hourstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
  3608. $hourstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3609. $hourstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3610. ### day:
  3611. $daystats = "SELECT to_char(timestamp, 'YYYY-MM-DD 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
  3612. $daystats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
  3613. $daystats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3614. $daystats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3615. ### week:
  3616. $weekstats = "SELECT date_trunc('week',timestamp) AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
  3617. $weekstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
  3618. $weekstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3619. $weekstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3620. ### month:
  3621. $monthstats = "SELECT to_char(timestamp, 'YYYY-MM-01 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
  3622. $monthstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
  3623. $monthstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3624. $monthstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3625. ### year:
  3626. $yearstats = "SELECT to_char(timestamp, 'YYYY-01-01 00:00:00') AS TIMESTAMP, SUM(VALUE::float) AS SUM, ";
  3627. $yearstats .= "AVG(VALUE::float) AS AVG, MIN(VALUE::float) AS MIN, MAX(VALUE::float) AS MAX, ";
  3628. $yearstats .= "COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3629. $yearstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3630. } elsif ($dbmodel eq "MYSQL") {
  3631. ### MYSQL Queries for Statistics ###
  3632. ### hour:
  3633. $hourstats = "SELECT date_format(timestamp, '%Y-%m-%d %H:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
  3634. $hourstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
  3635. $hourstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
  3636. $hourstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3637. ### day:
  3638. $daystats = "SELECT date_format(timestamp, '%Y-%m-%d 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
  3639. $daystats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
  3640. $daystats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
  3641. $daystats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3642. ### week:
  3643. $weekstats = "SELECT date_format(timestamp, '%Y-%m-%d 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
  3644. $weekstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
  3645. $weekstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
  3646. $weekstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' ";
  3647. $weekstats .= "GROUP BY date_format(timestamp, '%Y-%u 00:00:00') ORDER BY 1;";
  3648. ### month:
  3649. $monthstats = "SELECT date_format(timestamp, '%Y-%m-01 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
  3650. $monthstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
  3651. $monthstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
  3652. $monthstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3653. ### year:
  3654. $yearstats = "SELECT date_format(timestamp, '%Y-01-01 00:00:00') AS TIMESTAMP, SUM(CAST(VALUE AS DECIMAL(12,4))) AS SUM, ";
  3655. $yearstats .= "AVG(CAST(VALUE AS DECIMAL(12,4))) AS AVG, MIN(CAST(VALUE AS DECIMAL(12,4))) AS MIN, ";
  3656. $yearstats .= "MAX(CAST(VALUE AS DECIMAL(12,4))) AS MAX, COUNT(VALUE) AS COUNT FROM history WHERE READING = '$yaxis' ";
  3657. $yearstats .= "AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY 1 ORDER BY 1;";
  3658. } elsif ($dbmodel eq "SQLITE") {
  3659. ### SQLITE Queries for Statistics ###
  3660. ### hour:
  3661. $hourstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
  3662. $hourstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
  3663. $hourstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3664. $hourstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m-%d %H:00:00', TIMESTAMP);";
  3665. ### day:
  3666. $daystats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
  3667. $daystats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
  3668. $daystats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3669. $daystats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m-%d 00:00:00', TIMESTAMP);";
  3670. ### week:
  3671. $weekstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
  3672. $weekstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
  3673. $weekstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3674. $weekstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%W 00:00:00', TIMESTAMP);";
  3675. ### month:
  3676. $monthstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
  3677. $monthstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
  3678. $monthstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3679. $monthstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y-%m 00:00:00', TIMESTAMP);";
  3680. ### year:
  3681. $yearstats = "SELECT TIMESTAMP, SUM(CAST(VALUE AS FLOAT)) AS SUM, AVG(CAST(VALUE AS FLOAT)) AS AVG, ";
  3682. $yearstats .= "MIN(CAST(VALUE AS FLOAT)) AS MIN, MAX(CAST(VALUE AS FLOAT)) AS MAX, COUNT(VALUE) AS COUNT ";
  3683. $yearstats .= "FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3684. $yearstats .= "AND TIMESTAMP Between '$starttime' AND '$endtime' GROUP BY strftime('%Y 00:00:00', TIMESTAMP);";
  3685. } else {
  3686. $sql = "errordb";
  3687. }
  3688. if($userquery eq "getreadings") {
  3689. $sql = "SELECT distinct(reading) FROM history WHERE device = '".$device."'";
  3690. } elsif($userquery eq "getdevices") {
  3691. $sql = "SELECT distinct(device) FROM history";
  3692. } elsif($userquery eq "timerange") {
  3693. $sql = "SELECT ".$xaxis.", VALUE FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' AND TIMESTAMP Between '$starttime' AND '$endtime' ORDER BY TIMESTAMP;";
  3694. } elsif($userquery eq "hourstats") {
  3695. $sql = $hourstats;
  3696. } elsif($userquery eq "daystats") {
  3697. $sql = $daystats;
  3698. } elsif($userquery eq "weekstats") {
  3699. $sql = $weekstats;
  3700. } elsif($userquery eq "monthstats") {
  3701. $sql = $monthstats;
  3702. } elsif($userquery eq "yearstats") {
  3703. $sql = $yearstats;
  3704. } elsif($userquery eq "savechart") {
  3705. $sql = "INSERT INTO frontend (TYPE, NAME, VALUE) VALUES ('savedchart', '$savename', '$jsonChartConfig')";
  3706. } elsif($userquery eq "renamechart") {
  3707. $sql = "UPDATE frontend SET NAME = '$savename' WHERE ID = '$jsonChartConfig'";
  3708. } elsif($userquery eq "deletechart") {
  3709. $sql = "DELETE FROM frontend WHERE TYPE = 'savedchart' AND ID = '".$savename."'";
  3710. } elsif($userquery eq "updatechart") {
  3711. $sql = "UPDATE frontend SET VALUE = '$jsonChartConfig' WHERE ID = '".$savename."'";
  3712. } elsif($userquery eq "getcharts") {
  3713. $sql = "SELECT * FROM frontend WHERE TYPE = 'savedchart'";
  3714. } elsif($userquery eq "getTableData") {
  3715. if ($device ne '""' && $yaxis ne '""') {
  3716. $sql = "SELECT * FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3717. $sql .= "AND TIMESTAMP Between '$starttime' AND '$endtime'";
  3718. $sql .= " LIMIT '$paginglimit' OFFSET '$pagingstart'";
  3719. $countsql = "SELECT count(*) FROM history WHERE READING = '$yaxis' AND DEVICE = '$device' ";
  3720. $countsql .= "AND TIMESTAMP Between '$starttime' AND '$endtime'";
  3721. } elsif($device ne '""' && $yaxis eq '""') {
  3722. $sql = "SELECT * FROM history WHERE DEVICE = '$device' ";
  3723. $sql .= "AND TIMESTAMP Between '$starttime' AND '$endtime'";
  3724. $sql .= " LIMIT '$paginglimit' OFFSET '$pagingstart'";
  3725. $countsql = "SELECT count(*) FROM history WHERE DEVICE = '$device' ";
  3726. $countsql .= "AND TIMESTAMP Between '$starttime' AND '$endtime'";
  3727. } else {
  3728. $sql = "SELECT * FROM history";
  3729. $sql .= " WHERE TIMESTAMP Between '$starttime' AND '$endtime'";
  3730. $sql .= " LIMIT '$paginglimit' OFFSET '$pagingstart'";
  3731. $countsql = "SELECT count(*) FROM history";
  3732. $countsql .= " WHERE TIMESTAMP Between '$starttime' AND '$endtime'";
  3733. }
  3734. return ($sql, $countsql);
  3735. } else {
  3736. $sql = "error";
  3737. }
  3738. return $sql;
  3739. }
  3740. ################################################################
  3741. #
  3742. # Do the query
  3743. #
  3744. ################################################################
  3745. sub chartQuery($@) {
  3746. my ($sql, $countsql) = prepareSql(@_);
  3747. if ($sql eq "error") {
  3748. return jsonError("Could not setup SQL String. Maybe the Database is busy, please try again!");
  3749. } elsif ($sql eq "errordb") {
  3750. return jsonError("The Database Type is not supported!");
  3751. }
  3752. my ($hash, @a) = @_;
  3753. my $dbhf = DbLog_ConnectNewDBH($hash);
  3754. return if(!$dbhf);
  3755. my $totalcount;
  3756. if (defined $countsql && $countsql ne "") {
  3757. my $query_handle = $dbhf->prepare($countsql)
  3758. or return jsonError("Could not prepare statement: " . $dbhf->errstr . ", SQL was: " .$countsql);
  3759. $query_handle->execute()
  3760. or return jsonError("Could not execute statement: " . $query_handle->errstr);
  3761. my @data = $query_handle->fetchrow_array();
  3762. $totalcount = join(", ", @data);
  3763. }
  3764. # prepare the query
  3765. my $query_handle = $dbhf->prepare($sql)
  3766. or return jsonError("Could not prepare statement: " . $dbhf->errstr . ", SQL was: " .$sql);
  3767. # execute the query
  3768. $query_handle->execute()
  3769. or return jsonError("Could not execute statement: " . $query_handle->errstr);
  3770. my $columns = $query_handle->{'NAME'};
  3771. my $columncnt;
  3772. # When columns are empty but execution was successful, we have done a successful INSERT, UPDATE or DELETE
  3773. if($columns) {
  3774. $columncnt = scalar @$columns;
  3775. } else {
  3776. return '{"success": "true", "msg":"All ok"}';
  3777. }
  3778. my $i = 0;
  3779. my $jsonstring = '{"data":[';
  3780. while ( my @data = $query_handle->fetchrow_array()) {
  3781. if($i == 0) {
  3782. $jsonstring .= '{';
  3783. } else {
  3784. $jsonstring .= ',{';
  3785. }
  3786. for ($i = 0; $i < $columncnt; $i++) {
  3787. $jsonstring .= '"';
  3788. $jsonstring .= uc($query_handle->{NAME}->[$i]);
  3789. $jsonstring .= '":';
  3790. if (defined $data[$i]) {
  3791. my $fragment = substr($data[$i],0,1);
  3792. if ($fragment eq "{") {
  3793. $jsonstring .= $data[$i];
  3794. } else {
  3795. $jsonstring .= '"'.$data[$i].'"';
  3796. }
  3797. } else {
  3798. $jsonstring .= '""'
  3799. }
  3800. if($i != ($columncnt -1)) {
  3801. $jsonstring .= ',';
  3802. }
  3803. }
  3804. $jsonstring .= '}';
  3805. }
  3806. $dbhf->disconnect();
  3807. $jsonstring .= ']';
  3808. if (defined $totalcount && $totalcount ne "") {
  3809. $jsonstring .= ',"totalCount": '.$totalcount.'}';
  3810. } else {
  3811. $jsonstring .= '}';
  3812. }
  3813. return $jsonstring;
  3814. }
  3815. #########################
  3816. sub DbLog_fhemwebFn($$$$) {
  3817. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  3818. my $ret;
  3819. my $newIdx=1;
  3820. while($defs{"SVG_${d}_$newIdx"}) {
  3821. $newIdx++;
  3822. }
  3823. my $name = "SVG_${d}_$newIdx";
  3824. $ret .= FW_pH("cmd=define $name SVG $d:templateDB:HISTORY;".
  3825. "set $name copyGplotFile&detail=$name",
  3826. "<div class=\"dval\">Create SVG plot from DbLog</div>", 0, "dval", 1);
  3827. return $ret;
  3828. }
  3829. sub DbLog_sampleDataFn($$$$$) {
  3830. my ($dlName, $dlog, $max, $conf, $wName) = @_;
  3831. my $desc = "Device:Reading";
  3832. my @htmlArr;
  3833. my @example;
  3834. my @colregs;
  3835. my $counter;
  3836. my $currentPresent = AttrVal($dlName,'DbLogType','History');
  3837. my $dbhf = DbLog_ConnectNewDBH($defs{$dlName});
  3838. return if(!$dbhf);
  3839. # check presence of table current
  3840. # avoids fhem from crash if table 'current' is not present and attr DbLogType is set to /Current/
  3841. my $prescurr = eval {$dbhf->selectrow_array("select count(*) from current");} || 0;
  3842. Log3($dlName, 5, "DbLog $dlName: Table current present : $prescurr (0 = not present or no content)");
  3843. if($currentPresent =~ m/Current|SampleFill/ && $prescurr) {
  3844. # Table Current present, use it for sample data
  3845. my $query = "select device,reading from current where device <> '' group by device,reading";
  3846. my $sth = $dbhf->prepare( $query );
  3847. $sth->execute();
  3848. while (my @line = $sth->fetchrow_array()) {
  3849. $counter++;
  3850. push (@example, join (" ",@line)) if($counter <= 8); # show max 8 examples
  3851. push (@colregs, "$line[0]:$line[1]"); # push all eventTypes to selection list
  3852. }
  3853. $dbhf->disconnect();
  3854. my $cols = join(",", sort { "\L$a" cmp "\L$b" } @colregs);
  3855. $max = 8 if($max > 8);
  3856. for(my $r=0; $r < $max; $r++) {
  3857. my @f = split(":", ($dlog->[$r] ? $dlog->[$r] : ":::"), 4);
  3858. my $ret = "";
  3859. $ret .= SVG_sel("par_${r}_0", $cols, "$f[0]:$f[1]");
  3860. # $ret .= SVG_txt("par_${r}_2", "", $f[2], 1); # Default not yet implemented
  3861. # $ret .= SVG_txt("par_${r}_3", "", $f[3], 3); # Function
  3862. # $ret .= SVG_txt("par_${r}_4", "", $f[4], 3); # RegExp
  3863. push @htmlArr, $ret;
  3864. }
  3865. } else {
  3866. # Table Current not present, so create an empty input field
  3867. push @example, "No sample data due to missing table 'Current'";
  3868. $max = 8 if($max > 8);
  3869. for(my $r=0; $r < $max; $r++) {
  3870. my @f = split(":", ($dlog->[$r] ? $dlog->[$r] : ":::"), 4);
  3871. my $ret = "";
  3872. no warnings 'uninitialized'; # Forum:74690, bug unitialized
  3873. $ret .= SVG_txt("par_${r}_0", "", "$f[0]:$f[1]:$f[2]:$f[3]", 20);
  3874. use warnings;
  3875. # $ret .= SVG_txt("par_${r}_2", "", $f[2], 1); # Default not yet implemented
  3876. # $ret .= SVG_txt("par_${r}_3", "", $f[3], 3); # Function
  3877. # $ret .= SVG_txt("par_${r}_4", "", $f[4], 3); # RegExp
  3878. push @htmlArr, $ret;
  3879. }
  3880. }
  3881. return ($desc, \@htmlArr, join("<br>", @example));
  3882. }
  3883. #
  3884. # get <dbLog> ReadingsVal <device> <reading> <default>
  3885. # get <dbLog> ReadingsTimestamp <device> <reading> <default>
  3886. #
  3887. sub dbReadings($@) {
  3888. my($hash,@a) = @_;
  3889. my $dbhf = DbLog_ConnectNewDBH($hash);
  3890. return if(!$dbhf);
  3891. return 'Wrong Syntax for ReadingsVal!' unless defined($a[4]);
  3892. my $DbLogType = AttrVal($a[0],'DbLogType','current');
  3893. my $query;
  3894. if (lc($DbLogType) =~ m(current) ) {
  3895. $query = "select VALUE,TIMESTAMP from current where DEVICE= '$a[2]' and READING= '$a[3]'";
  3896. } else {
  3897. $query = "select VALUE,TIMESTAMP from history where DEVICE= '$a[2]' and READING= '$a[3]' order by TIMESTAMP desc limit 1";
  3898. }
  3899. my ($reading,$timestamp) = $dbhf->selectrow_array($query);
  3900. $dbhf->disconnect();
  3901. $reading = (defined($reading)) ? $reading : $a[4];
  3902. $timestamp = (defined($timestamp)) ? $timestamp : $a[4];
  3903. return $reading if $a[1] eq 'ReadingsVal';
  3904. return $timestamp if $a[1] eq 'ReadingsTimestamp';
  3905. return "Syntax error: $a[1]";
  3906. }
  3907. ################################################################
  3908. # benutzte DB-Feldlängen in Helper und Internals setzen
  3909. ################################################################
  3910. sub setinternalcols ($){
  3911. my ($hash)= @_;
  3912. my $name = $hash->{NAME};
  3913. $hash->{HELPER}{DEVICECOL} = $columns{DEVICE};
  3914. $hash->{HELPER}{TYPECOL} = $columns{TYPE};
  3915. $hash->{HELPER}{EVENTCOL} = AttrVal($name, "colEvent", $columns{EVENT});
  3916. $hash->{HELPER}{READINGCOL} = AttrVal($name, "colReading", $columns{READING});
  3917. $hash->{HELPER}{VALUECOL} = AttrVal($name, "colValue", $columns{VALUE});
  3918. $hash->{HELPER}{UNITCOL} = $columns{UNIT};
  3919. $hash->{COLUMNS} = "field length used for Device: $hash->{HELPER}{DEVICECOL}, Type: $hash->{HELPER}{TYPECOL}, Event: $hash->{HELPER}{EVENTCOL}, Reading: $hash->{HELPER}{READINGCOL}, Value: $hash->{HELPER}{VALUECOL}, Unit: $hash->{HELPER}{UNITCOL} ";
  3920. # Statusbit "Columns sind gesetzt"
  3921. $hash->{HELPER}{COLSET} = 1;
  3922. return;
  3923. }
  3924. ################################################################
  3925. # reopen DB-Connection nach Ablauf set ... reopen [n] seconds
  3926. ################################################################
  3927. sub reopen ($){
  3928. my ($hash) = @_;
  3929. my $name = $hash->{NAME};
  3930. my $async = AttrVal($name, "asyncMode", undef);
  3931. RemoveInternalTimer($hash, "reopen");
  3932. if(DbLog_ConnectPush($hash)) {
  3933. # Statusbit "Kein Schreiben in DB erlauben" löschen
  3934. my $delay = delete $hash->{HELPER}{REOPEN_RUNS};
  3935. Log3($name, 2, "DbLog $name: Database connection reopened (it was $delay seconds closed).");
  3936. readingsSingleUpdate($hash, "state", "reopened", 1);
  3937. $hash->{HELPER}{OLDSTATE} = "reopened";
  3938. DbLog_execmemcache($hash) if($async);
  3939. } else {
  3940. InternalTimer(gettimeofday()+30, "reopen", $hash, 0);
  3941. }
  3942. return;
  3943. }
  3944. ################################################################
  3945. # check ob primary key genutzt wird
  3946. ################################################################
  3947. sub checkUsePK ($$){
  3948. my ($hash,$dbh) = @_;
  3949. my $name = $hash->{NAME};
  3950. my $dbconn = $hash->{dbconn};
  3951. my $upkh = 0;
  3952. my $upkc = 0;
  3953. my (@pkh,@pkc);
  3954. my $db = (split("=",(split(";",$dbconn))[0]))[1];
  3955. eval {@pkh = $dbh->primary_key( undef, undef, 'history' );};
  3956. eval {@pkc = $dbh->primary_key( undef, undef, 'current' );};
  3957. my $pkh = (!@pkh || @pkh eq "")?"none":join(",",@pkh);
  3958. my $pkc = (!@pkc || @pkc eq "")?"none":join(",",@pkc);
  3959. $pkh =~ tr/"//d;
  3960. $pkc =~ tr/"//d;
  3961. $upkh = 1 if(@pkh && @pkh ne "none");
  3962. $upkc = 1 if(@pkc && @pkc ne "none");
  3963. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key used in $db.history: $pkh";
  3964. Log3 $hash->{NAME}, 5, "DbLog $name -> Primary Key used in $db.current: $pkc";
  3965. return ($upkh,$upkc,$pkh,$pkc);
  3966. }
  3967. 1;
  3968. =pod
  3969. =item helper
  3970. =item summary logs events into a database
  3971. =item summary_DE loggt Events in eine Datenbank
  3972. =begin html
  3973. <a name="DbLog"></a>
  3974. <h3>DbLog</h3>
  3975. <ul>
  3976. <br>
  3977. With DbLog events can be stored in a database. SQLite, MySQL/MariaDB and PostgreSQL are supported databases. <br><br>
  3978. <b>Prereqisites</b> <br><br>
  3979. The Perl-modules <code>DBI</code> and <code>DBD::&lt;dbtype&gt;</code> are needed to be installed (use <code>cpan -i &lt;module&gt;</code>
  3980. if your distribution does not have it).
  3981. <br><br>
  3982. On a debian based system you may install these modules for instance by: <br><br>
  3983. <ul>
  3984. <table>
  3985. <colgroup> <col width=5%> <col width=95%> </colgroup>
  3986. <tr><td> <b>DBI</b> </td><td>: <code> sudo apt-get install libdbi-perl </code> </td></tr>
  3987. <tr><td> <b>MySQL</b> </td><td>: <code> sudo apt-get install [mysql-server] mysql-client libdbd-mysql libdbd-mysql-perl </code> (mysql-server only if you use a local MySQL-server installation) </td></tr>
  3988. <tr><td> <b>SQLite</b> </td><td>: <code> sudo apt-get install sqlite3 libdbi-perl libdbd-sqlite3-perl </code> </td></tr>
  3989. <tr><td> <b>PostgreSQL</b> </td><td>: <code> sudo apt-get install libdbd-pg-perl </code> </td></tr>
  3990. </table>
  3991. </ul>
  3992. <br>
  3993. <br>
  3994. <b>Preparations</b> <br><br>
  3995. At first you need to setup the database. <br>
  3996. Sample code and Scripts to prepare a MySQL/PostgreSQL/SQLite database you can find in <code>contrib/dblog/&lt;DBType&gt;_create.sql</code>.
  3997. The database contains two tables: <code>current</code> and <code>history</code>. <br>
  3998. The latter contains all events whereas the former only contains the last event for any given reading and device.
  3999. Please consider the <a href="#DbLogattr">attribute</a> DbLogType implicitly to determine the usage of tables
  4000. <code>current</code> and <code>history</code>.
  4001. <br><br>
  4002. The columns have the following meaning:: <br><br>
  4003. <ul>
  4004. <table>
  4005. <colgroup> <col width=5%> <col width=95%> </colgroup>
  4006. <tr><td> TIMESTAMP </td><td>: timestamp of event, e.g. <code>2007-12-30 21:45:22</code> </td></tr>
  4007. <tr><td> DEVICE </td><td>: device name, e.g. <code>Wetterstation</code> </td></tr>
  4008. <tr><td> TYPE </td><td>: device type, e.g. <code>KS300</code> </code> </td></tr>
  4009. <tr><td> EVENT </td><td>: event specification as full string, e.g. <code>humidity: 71 (%)</code> </td></tr>
  4010. <tr><td> READING </td><td>: name of reading extracted from event, e.g. <code>humidity</code> </td></tr>
  4011. <tr><td> VALUE </td><td>: actual reading extracted from event, e.g. <code>71</code> </td></tr>
  4012. <tr><td> UNIT </td><td>: unit extracted from event, e.g. <code>%</code> </td></tr>
  4013. </table>
  4014. </ul>
  4015. <br>
  4016. <br>
  4017. <b>create index</b> <br>
  4018. Due to reading performance, e.g. on creation of SVG-plots, it is very important that the <b>index "Search_Idx"</b>
  4019. or a comparable index (e.g. a primary key) is applied. A sample code for creation of that index is also available at the mentioned scripts in
  4020. <code>contrib/dblog/&lt;DBType&gt;_create.sql</code>. <br><br>
  4021. The index "Search_Idx" can be created, e.g. in database 'fhem', by these statements (also subsequently): <br><br>
  4022. <ul>
  4023. <table>
  4024. <colgroup> <col width=5%> <col width=95%> </colgroup>
  4025. <tr><td> <b>MySQL</b> </td><td>: <code> CREATE INDEX Search_Idx ON `fhem`.`history` (DEVICE, READING, TIMESTAMP); </code> </td></tr>
  4026. <tr><td> <b>SQLite</b> </td><td>: <code> CREATE INDEX Search_Idx ON `history` (DEVICE, READING, TIMESTAMP); </code> </td></tr>
  4027. <tr><td> <b>PostgreSQL</b> </td><td>: <code> CREATE INDEX "Search_Idx" ON history USING btree (device, reading, "timestamp"); </code> </td></tr>
  4028. </table>
  4029. </ul>
  4030. <br>
  4031. For the connection to the database a <b>configuration file</b> is used.
  4032. The configuration is stored in a separate file to avoid storing the password in the main configuration file and to have it
  4033. visible in the output of the <a href="https://fhem.de/commandref.html#list">list</a> command.
  4034. <br><br>
  4035. The <b>configuration file</b> should be copied e.g. to /opt/fhem and has the following structure you have to customize
  4036. suitable to your conditions (decomment the appropriate raws and adjust it): <br><br>
  4037. <pre>
  4038. ####################################################################################
  4039. # database configuration file
  4040. #
  4041. # NOTE:
  4042. # If you don't use a value for user / password please delete the leading hash mark
  4043. # and write 'user => ""' respectively 'password => ""' instead !
  4044. #
  4045. #
  4046. ## for MySQL
  4047. ####################################################################################
  4048. #%dbconfig= (
  4049. # connection => "mysql:database=fhem;host=db;port=3306",
  4050. # user => "fhemuser",
  4051. # password => "fhempassword",
  4052. # # optional enable(1) / disable(0) UTF-8 support (at least V 4.042 is necessary)
  4053. # utf8 => 1
  4054. #);
  4055. ####################################################################################
  4056. #
  4057. ## for PostgreSQL
  4058. ####################################################################################
  4059. #%dbconfig= (
  4060. # connection => "Pg:database=fhem;host=localhost",
  4061. # user => "fhemuser",
  4062. # password => "fhempassword"
  4063. #);
  4064. ####################################################################################
  4065. #
  4066. ## for SQLite (username and password stay empty for SQLite)
  4067. ####################################################################################
  4068. #%dbconfig= (
  4069. # connection => "SQLite:dbname=/opt/fhem/fhem.db",
  4070. # user => "",
  4071. # password => ""
  4072. #);
  4073. ####################################################################################
  4074. </pre>
  4075. <br>
  4076. <a name="DbLogdefine"></a>
  4077. <b>Define</b>
  4078. <ul>
  4079. <br>
  4080. <code>define &lt;name&gt; DbLog &lt;configfilename&gt; &lt;regexp&gt;</code>
  4081. <br><br>
  4082. <code>&lt;configfilename&gt;</code> is the prepared <b>configuration file</b>. <br>
  4083. <code>&lt;regexp&gt;</code> is identical to the specification of regex in the <a href="https://fhem.de/commandref.html#FileLog">FileLog</a> definition.
  4084. <br><br>
  4085. <b>Example:</b>
  4086. <ul>
  4087. <code>define myDbLog DbLog /etc/fhem/db.conf .*:.*</code><br>
  4088. all events will stored into the database
  4089. </ul>
  4090. <br>
  4091. After you have defined your DbLog-device it is recommended to run the <b>configuration check</b> <br><br>
  4092. <ul>
  4093. <code>set &lt;name&gt; configCheck</code> <br>
  4094. </ul>
  4095. <br>
  4096. This check reports some important settings and gives recommendations back to you if proposals are indentified.
  4097. (Available for MySQL, PostgreSQL) <br><br>
  4098. DbLog distinguishes between the synchronous (default) and asynchronous logmode. The logmode is adjustable by the
  4099. <a href="#DbLogattr">attribute</a> asyncMode. Since version 2.13.5 DbLog is supporting primary key (PK) set in table
  4100. current or history. If you want use PostgreSQL with PK it has to be at lest version 9.5.
  4101. <br><br>
  4102. The content of VALUE will be optimized for automated post-processing, e.g. <code>yes</code> is translated to <code>1</code>
  4103. <br><br>
  4104. The stored values can be retrieved by the following code like FileLog:<br>
  4105. <ul>
  4106. <code>get myDbLog - - 2012-11-10 2012-11-10 KS300:temperature::</code>
  4107. </ul>
  4108. <br>
  4109. <b>transfer FileLog-data to DbLog </b> <br><br>
  4110. There is the special module 98_FileLogConvert.pm available to transfer filelog-data to the DbLog-database. <br>
  4111. The module can be downloaded <a href="https://svn.fhem.de/trac/browser/trunk/fhem/contrib/98_FileLogConvert.pm"> here</a>
  4112. or from directory ./contrib instead.
  4113. Further informations and help you can find in the corresponding <a href="https://forum.fhem.de/index.php/topic,66383.0.html">
  4114. Forumthread </a>. <br><br><br>
  4115. <b>Reporting and Management of DbLog database content</b> <br><br>
  4116. By using <a href="https://fhem.de/commandref.html#SVG">SVG</a> database content can be visualized. <br>
  4117. Beyond that the module <a href="https://fhem.de/commandref.html#DbRep">DbRep</a> can be used to prepare tabular
  4118. database reports or you can manage the database content with available functions of that module.
  4119. <br><br>
  4120. </ul>
  4121. <br>
  4122. <br>
  4123. <a name="DbLogset"></a>
  4124. <b>Set</b>
  4125. <ul>
  4126. <code>set &lt;name&gt; addLog &lt;devspec&gt;:&lt;Reading&gt; [Value] </code><br><br>
  4127. <ul> Inserts an additional log entry of a device/reading combination into the database.
  4128. Optionally you can enter a "Value" that is used as reading value for the dataset. If the value isn't specified (default),
  4129. the current value of the specified reading will be inserted into the database. The field "$EVENT" will be filled automatically
  4130. by "addLog". The device can be declared by a <a href="#devspec">device specification (devspec)</a>.
  4131. "Reading" will be evaluated as regular expression.
  4132. By the addLog-command NO additional events will be created !<br><br>
  4133. <b>Examples:</b> <br>
  4134. set &lt;name&gt; addLog SMA_Energymeter:Bezug_Wirkleistung <br>
  4135. set &lt;name&gt; addLog TYPE=SSCam:state <br>
  4136. set &lt;name&gt; addLog MyWetter:(fc10.*|fc8.*) <br>
  4137. set &lt;name&gt; addLog MyWetter:(wind|wind_ch.*) 20 <br>
  4138. set &lt;name&gt; addLog TYPE=CUL_HM:FILTER=model=HM-CC-RT-DN:FILTER=subType!=(virtual|):(measured-temp|desired-temp|actuator) <br>
  4139. </ul><br>
  4140. <code>set &lt;name&gt; clearReadings </code><br><br>
  4141. <ul> This function clears readings which were created by different DbLog-functions. </ul><br>
  4142. <code>set &lt;name&gt; commitCache </code><br><br>
  4143. <ul>In asynchronous mode (<a href="#DbLogattr">attribute</a> asyncMode=1), the cached data in memory will be written into the database
  4144. and subsequently the cache will be cleared. Thereby the internal timer for the asynchronous mode Modus will be set new.
  4145. The command can be usefull in case of you want to write the cached data manually or e.g. by an AT-device on a defined
  4146. point of time into the database. </ul><br>
  4147. <code>set &lt;name&gt; configCheck </code><br><br>
  4148. <ul>This command checks some important settings and gives recommendations back to you if proposals are indentified.
  4149. (available for MySQL, PostgreSQL) </ul><br>
  4150. <code>set &lt;name&gt; count </code><br/><br/>
  4151. <ul>Count records in tables current and history and write results into readings countCurrent and countHistory.</ul><br/>
  4152. <code>set &lt;name&gt; countNbl </code><br/><br/>
  4153. <ul>The non-blocking execution of "set &lt;name&gt; count".</ul><br/>
  4154. <code>set &lt;name&gt; deleteOldDays &lt;n&gt;</code><br/><br/>
  4155. <ul>Delete records from history older than &lt;n&gt; days. Number of deleted records will be written into reading lastRowsDeleted.</ul><br/>
  4156. <code>set &lt;name&gt; deleteOldDaysNbl &lt;n&gt;</code><br/><br/>
  4157. <ul>Is identical to function "deleteOldDays" whereupon deleteOldDaysNbl will be executed non-blocking. </ul><br/>
  4158. <code>set &lt;name&gt; eraseReadings </code><br><br>
  4159. <ul> This function deletes all readings except reading "state". </ul><br>
  4160. <a name="DbLogsetexportCache"></a>
  4161. <code>set &lt;name&gt; exportCache [nopurge | purgeCache] </code><br><br>
  4162. <ul>If DbLog is operating in asynchronous mode, it's possible to exoprt the cache content into a textfile.
  4163. The file will be written to the directory (global->modpath)/log/ by default setting. The detination directory can be
  4164. changed by the <a href="#DbLogattr">attribute</a> expimpdir. <br>
  4165. The filename will be generated automatically and is built by a prefix "cache_", followed by DbLog-devicename and the
  4166. present timestmp, e.g. "cache_LogDB_2017-03-23_22-13-55". <br>
  4167. There are two options possible, "nopurge" respectively "purgeCache". The option determines whether the cache content
  4168. will be deleted after export or not.
  4169. Using option "nopurge" (default) the cache content will be preserved. </ul><br>
  4170. <code>set &lt;name&gt; importCachefile &lt;file&gt; </code><br><br>
  4171. <ul>Imports an textfile into the database which has been written by the "exportCache" function.
  4172. The allocatable files will be searched in directory (global->modpath)/log/ by default and a drop-down list will be
  4173. generated from the files which are found in the directory.
  4174. The source directory can be changed by the <a href="#DbLogattr">attribute</a> expimpdir. <br>
  4175. Only that files will be shown which are correlate on pattern starting with "cache_", followed by the DbLog-devicename. <br>
  4176. For example a file with the name "cache_LogDB_2017-03-23_22-13-55", will match if Dblog-device has name "LogDB". <br>
  4177. After the import has been successfully done, a prefix "impdone_" will be added at begin of the filename and this file
  4178. ddoesn't appear on the drop-down list anymore. <br>
  4179. If you want to import a cachefile from another source database, you may adapt the filename so it fits the search criteria
  4180. "DbLog-Device" in its name. After renaming the file appeares again on the drop-down list. </ul><br>
  4181. <code>set &lt;name&gt; listCache </code><br><br>
  4182. <ul>If DbLog is set to asynchronous mode (attribute asyncMode=1), you can use that command to list the events are cached in memory.</ul><br>
  4183. <code>set &lt;name&gt; purgeCache </code><br><br>
  4184. <ul>In asynchronous mode (<a href="#DbLogattr">attribute</a> asyncMode=1), the in memory cached data will be deleted.
  4185. With this command data won't be written from cache into the database. </ul><br>
  4186. <code>set &lt;name&gt; reduceLog &lt;n&gt; [average[=day]] [exclude=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...]</code> <br><br>
  4187. <ul>Reduces records older than &lt;n&gt; days to one record each hour (the 1st) per device & reading. <br><br>
  4188. <b>CAUTION:</b> It is strongly recommended to check if the default INDEX 'Search_Idx' exists on the table 'history'! <br>
  4189. The execution of this command may take (without INDEX) extremely long. FHEM will be <b>blocked completely</b> after issuing the command to completion ! <br><br>
  4190. With the optional argument 'average' not only the records will be reduced, but all numerical values of an hour
  4191. will be reduced to a single average. <br>
  4192. With the optional argument 'average=day' not only the records will be reduced, but all numerical values of a
  4193. day will be reduced to a single average. (implies 'average') <br>
  4194. You can optional set the last argument to "EXCLUDE=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...."
  4195. to exclude device/readings from reduceLog. <br>
  4196. You can optional set the last argument to "INCLUDE=Database-deviceRegExp:Database-ReadingRegExp" to delimit
  4197. the SELECT statement which is executet on the database. This reduces the system RAM load and increases the
  4198. performance. (Wildcards are % and _) <br>
  4199. </ul><br>
  4200. <code>set &lt;name&gt; reduceLogNbl &lt;n&gt; [average[=day]] [exclude=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...]</code> <br><br>
  4201. <ul>Same function as "set &lt;name&gt; reduceLog" but FHEM won't be blocked due to this function is implemented non-blocking ! <br>
  4202. </ul><br>
  4203. <code>set &lt;name&gt; reopen [n] </code><br/><br/>
  4204. <ul>Perform a database disconnect and immediate reconnect to clear cache and flush journal file if no time [n] was set. <br>
  4205. If optionally a delay time of [n] seconds was set, the database connection will be disconnect immediately but it was only reopened
  4206. after [n] seconds. In synchronous mode the events won't saved during that time. In asynchronous mode the events will be
  4207. stored in the memory cache and saved into database after the reconnect was done. </ul><br/>
  4208. <code>set &lt;name&gt; rereadcfg </code><br/><br/>
  4209. <ul>Perform a database disconnect and immediate reconnect to clear cache and flush journal file.<br/>
  4210. Probably same behavior als reopen, but rereadcfg will read the configuration data before reconnect.</ul><br/>
  4211. <code>set &lt;name&gt; userCommand &lt;validSqlStatement&gt;</code><br/><br/>
  4212. <ul><b>DO NOT USE THIS COMMAND UNLESS YOU REALLY (REALLY!) KNOW WHAT YOU ARE DOING!!!</b><br/><br/>
  4213. Performs any (!!!) sql statement on connected database. Usercommand and result will be written into
  4214. corresponding readings.</br>
  4215. The result can only be a single line. If the SQL-Statement seems to deliver a multiline result, it can be suitable
  4216. to use the analysis module <a href=https://fhem.de/commandref.html#DbRep>DbRep</a>.</br>
  4217. If the database interface delivers no result (undef), the reading "userCommandResult" contains the message
  4218. "no result".
  4219. </ul><br/>
  4220. </ul><br>
  4221. <a name="DbLogget"></a>
  4222. <b>Get</b>
  4223. <ul>
  4224. <code>get &lt;name&gt; ReadingsVal&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;device&gt; &lt;reading&gt; &lt;default&gt;</code><br/>
  4225. <code>get &lt;name&gt; ReadingsTimestamp &lt;device&gt; &lt;reading&gt; &lt;default&gt;</code><br/>
  4226. <br/>
  4227. Retrieve one single value, use and syntax are similar to ReadingsVal() and ReadingsTimestamp() functions.<br/>
  4228. </ul>
  4229. <br/>
  4230. <br/>
  4231. <ul>
  4232. <code>get &lt;name&gt; &lt;infile&gt; &lt;outfile&gt; &lt;from&gt;
  4233. &lt;to&gt; &lt;column_spec&gt; </code>
  4234. <br><br>
  4235. Read data from the Database, used by frontends to plot data without direct
  4236. access to the Database.<br>
  4237. <ul>
  4238. <li>&lt;in&gt;<br>
  4239. A dummy parameter for FileLog compatibility. Sessing by defaultto <code>-</code><br>
  4240. <ul>
  4241. <li>current: reading actual readings from table "current"</li>
  4242. <li>history: reading history readings from table "history"</li>
  4243. <li>-: identical to "history"</li>
  4244. </ul>
  4245. </li>
  4246. <li>&lt;out&gt;<br>
  4247. A dummy parameter for FileLog compatibility. Setting by default to <code>-</code>
  4248. to check the output for plot-computing.<br>
  4249. Set it to the special keyword
  4250. <code>all</code> to get all columns from Database.
  4251. <ul>
  4252. <li>ALL: get all colums from table, including a header</li>
  4253. <li>Array: get the columns as array of hashes</li>
  4254. <li>INT: internally used by generating plots</li>
  4255. <li>-: default</li>
  4256. </ul>
  4257. </li>
  4258. <li>&lt;from&gt; / &lt;to&gt;<br>
  4259. Used to select the data. Please use the following timeformat or
  4260. an initial substring of it:<br>
  4261. <ul><code>YYYY-MM-DD_HH24:MI:SS</code></ul></li>
  4262. <li>&lt;column_spec&gt;<br>
  4263. For each column_spec return a set of data separated by
  4264. a comment line on the current connection.<br>
  4265. Syntax: &lt;device&gt;:&lt;reading&gt;:&lt;default&gt;:&lt;fn&gt;:&lt;regexp&gt;<br>
  4266. <ul>
  4267. <li>&lt;device&gt;<br>
  4268. The name of the device. Case sensitive. Using a the joker "%" is supported.</li>
  4269. <li>&lt;reading&gt;<br>
  4270. The reading of the given device to select. Case sensitive. Using a the joker "%" is supported.
  4271. </li>
  4272. <li>&lt;default&gt;<br>
  4273. no implemented yet
  4274. </li>
  4275. <li>&lt;fn&gt;
  4276. One of the following:
  4277. <ul>
  4278. <li>int<br>
  4279. Extract the integer at the beginning of the string. Used e.g.
  4280. for constructs like 10%</li>
  4281. <li>int&lt;digit&gt;<br>
  4282. Extract the decimal digits including negative character and
  4283. decimal point at the beginning og the string. Used e.g.
  4284. for constructs like 15.7&deg;C</li>
  4285. <li>delta-h / delta-d<br>
  4286. Return the delta of the values for a given hour or a given day.
  4287. Used if the column contains a counter, as is the case for the
  4288. KS300 rain column.</li>
  4289. <li>delta-ts<br>
  4290. Replaced the original value with a measured value of seconds since
  4291. the last and the actual logentry.
  4292. </li>
  4293. </ul></li>
  4294. <li>&lt;regexp&gt;<br>
  4295. The string is evaluated as a perl expression. The regexp is executed
  4296. before &lt;fn&gt; parameter.<br>
  4297. Note: The string/perl expression cannot contain spaces,
  4298. as the part after the space will be considered as the
  4299. next column_spec.<br>
  4300. <b>Keywords</b>
  4301. <li>$val is the current value returned from the Database.</li>
  4302. <li>$ts is the current timestamp returned from the Database.</li>
  4303. <li>This Logentry will not print out if $val contains th keyword "hide".</li>
  4304. <li>This Logentry will not print out and not used in the following processing
  4305. if $val contains th keyword "ignore".</li>
  4306. </li>
  4307. </ul></li>
  4308. </ul>
  4309. <br><br>
  4310. Examples:
  4311. <ul>
  4312. <li><code>get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature</code></li>
  4313. <li><code>get myDbLog current ALL - - %:temperature</code></li><br>
  4314. you will get all actual readings "temperature" from all logged devices.
  4315. Be careful by using "history" as inputfile because a long execution time will be expected!
  4316. <li><code>get myDbLog - - 2012-11-10_10 2012-11-10_20 KS300:temperature::int1</code><br>
  4317. like from 10am until 08pm at 10.11.2012</li>
  4318. <li><code>get myDbLog - all 2012-11-10 2012-11-20 KS300:temperature</code></li>
  4319. <li><code>get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature KS300:rain::delta-h KS300:rain::delta-d</code></li>
  4320. <li><code>get myDbLog - - 2012-11-10 2012-11-20 MyFS20:data:::$val=~s/(on|off).*/$1eq"on"?1:0/eg</code><br>
  4321. return 1 for all occurance of on* (on|on-for-timer etc) and 0 for all off*</li>
  4322. <li><code>get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg</code><br>
  4323. Example of OWAD: value like this: <code>"A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"</code><br>
  4324. and output for port B is like this: <code>2012-11-20_10:23:54 66.647</code></li>
  4325. <li><code>get DbLog - - 2013-05-26 2013-05-28 Pumpe:data::delta-ts:$val=~s/on/hide/</code><br>
  4326. Setting up a "Counter of Uptime". The function delta-ts gets the seconds between the last and the
  4327. actual logentry. The keyword "hide" will hide the logentry of "on" because this time
  4328. is a "counter of Downtime"</li>
  4329. </ul>
  4330. <br><br>
  4331. </ul>
  4332. <b>Get</b> when used for webcharts
  4333. <ul>
  4334. <code>get &lt;name&gt; &lt;infile&gt; &lt;outfile&gt; &lt;from&gt;
  4335. &lt;to&gt; &lt;device&gt; &lt;querytype&gt; &lt;xaxis&gt; &lt;yaxis&gt; &lt;savename&gt; </code>
  4336. <br><br>
  4337. Query the Database to retrieve JSON-Formatted Data, which is used by the charting frontend.
  4338. <br>
  4339. <ul>
  4340. <li>&lt;name&gt;<br>
  4341. The name of the defined DbLog, like it is given in fhem.cfg.</li>
  4342. <li>&lt;in&gt;<br>
  4343. A dummy parameter for FileLog compatibility. Always set to <code>-</code></li>
  4344. <li>&lt;out&gt;<br>
  4345. A dummy parameter for FileLog compatibility. Set it to <code>webchart</code>
  4346. to use the charting related get function.
  4347. </li>
  4348. <li>&lt;from&gt; / &lt;to&gt;<br>
  4349. Used to select the data. Please use the following timeformat:<br>
  4350. <ul><code>YYYY-MM-DD_HH24:MI:SS</code></ul></li>
  4351. <li>&lt;device&gt;<br>
  4352. A string which represents the device to query.</li>
  4353. <li>&lt;querytype&gt;<br>
  4354. A string which represents the method the query should use. Actually supported values are: <br>
  4355. <code>getreadings</code> to retrieve the possible readings for a given device<br>
  4356. <code>getdevices</code> to retrieve all available devices<br>
  4357. <code>timerange</code> to retrieve charting data, which requires a given xaxis, yaxis, device, to and from<br>
  4358. <code>savechart</code> to save a chart configuration in the database. Requires a given xaxis, yaxis, device, to and from, and a 'savename' used to save the chart<br>
  4359. <code>deletechart</code> to delete a saved chart. Requires a given id which was set on save of the chart<br>
  4360. <code>getcharts</code> to get a list of all saved charts.<br>
  4361. <code>getTableData</code> to get jsonformatted data from the database. Uses paging Parameters like start and limit.<br>
  4362. <code>hourstats</code> to get statistics for a given value (yaxis) for an hour.<br>
  4363. <code>daystats</code> to get statistics for a given value (yaxis) for a day.<br>
  4364. <code>weekstats</code> to get statistics for a given value (yaxis) for a week.<br>
  4365. <code>monthstats</code> to get statistics for a given value (yaxis) for a month.<br>
  4366. <code>yearstats</code> to get statistics for a given value (yaxis) for a year.<br>
  4367. </li>
  4368. <li>&lt;xaxis&gt;<br>
  4369. A string which represents the xaxis</li>
  4370. <li>&lt;yaxis&gt;<br>
  4371. A string which represents the yaxis</li>
  4372. <li>&lt;savename&gt;<br>
  4373. A string which represents the name a chart will be saved with</li>
  4374. <li>&lt;chartconfig&gt;<br>
  4375. A jsonstring which represents the chart to save</li>
  4376. <li>&lt;pagingstart&gt;<br>
  4377. An integer used to determine the start for the sql used for query 'getTableData'</li>
  4378. <li>&lt;paginglimit&gt;<br>
  4379. An integer used to set the limit for the sql used for query 'getTableData'</li>
  4380. </ul>
  4381. <br><br>
  4382. Examples:
  4383. <ul>
  4384. <li><code>get logdb - webchart "" "" "" getcharts</code><br>
  4385. Retrieves all saved charts from the Database</li>
  4386. <li><code>get logdb - webchart "" "" "" getdevices</code><br>
  4387. Retrieves all available devices from the Database</li>
  4388. <li><code>get logdb - webchart "" "" ESA2000_LED_011e getreadings</code><br>
  4389. Retrieves all available Readings for a given device from the Database</li>
  4390. <li><code>get logdb - webchart 2013-02-11_00:00:00 2013-02-12_00:00:00 ESA2000_LED_011e timerange TIMESTAMP day_kwh</code><br>
  4391. Retrieves charting data, which requires a given xaxis, yaxis, device, to and from<br>
  4392. Will ouput a JSON like this: <code>[{'TIMESTAMP':'2013-02-11 00:10:10','VALUE':'0.22431388090756'},{'TIMESTAMP'.....}]</code></li>
  4393. <li><code>get logdb - webchart 2013-02-11_00:00:00 2013-02-12_00:00:00 ESA2000_LED_011e savechart TIMESTAMP day_kwh tageskwh</code><br>
  4394. Will save a chart in the database with the given name and the chart configuration parameters</li>
  4395. <li><code>get logdb - webchart "" "" "" deletechart "" "" 7</code><br>
  4396. Will delete a chart from the database with the given id</li>
  4397. </ul>
  4398. <br><br>
  4399. </ul>
  4400. <a name="DbLogattr"></a>
  4401. <b>Attributes</b>
  4402. <br><br>
  4403. <ul><b>addStateEvent</b>
  4404. <ul>
  4405. <code>attr &lt;device&gt; addStateEvent [0|1]
  4406. </code><br>
  4407. As you probably know the event associated with the state Reading is special, as the "state: "
  4408. string is stripped, i.e event is not "state: on" but just "on". <br>
  4409. Mostly it is desireable to get the complete event without "state: " stripped, so it is the default behavior of DbLog.
  4410. That means you will get state-event complete as "state: xxx". <br>
  4411. In some circumstances, e.g. older or special modules, it is a good idea to set addStateEvent to "0".
  4412. Try it if you have trouble with the default adjustment.
  4413. <br>
  4414. </ul>
  4415. </ul>
  4416. <br>
  4417. <ul><b>asyncMode</b>
  4418. <ul>
  4419. <code>attr &lt;device&gt; asyncMode [1|0]
  4420. </code><br>
  4421. This attribute determines the operation mode of DbLog. If asynchronous mode is active (asyncMode=1), the events which should be saved
  4422. at first will be cached in memory. After synchronisation time cycle (attribute syncInterval), or if the count limit of datasets in cache
  4423. is reached (attribute cacheLimit), the cached events get saved into the database using bulk insert.
  4424. If the database isn't available, the events will be cached in memeory furthermore, and tried to save into database again after
  4425. the next synchronisation time cycle if the database is available. <br>
  4426. In asynchronous mode the data insert into database will be executed non-blocking by a background process.
  4427. You can adjust the timeout value for this background process by attribute "timeout" (default 86400s). <br>
  4428. In synchronous mode (normal mode) the events won't be cached im memory and get saved into database immediately. If the database isn't
  4429. available the events are get lost. <br>
  4430. </ul>
  4431. </ul>
  4432. <br>
  4433. <ul><b>cacheEvents</b>
  4434. <ul>
  4435. <code>attr &lt;device&gt; cacheEvents [2|1|0]
  4436. </code><br>
  4437. <ul>
  4438. <li>cacheEvents=1: creates events of reading CacheUsage at point of time when a new dataset has been added to the cache. </li>
  4439. <li>cacheEvents=2: creates events of reading CacheUsage at point of time when in aychronous mode a new write cycle to the
  4440. database starts. In that moment CacheUsage contains the amount of datasets which will be written to
  4441. the database. </li><br>
  4442. </ul>
  4443. </ul>
  4444. </ul>
  4445. <br>
  4446. <ul><b>cacheLimit</b>
  4447. <ul>
  4448. <code>
  4449. attr &lt;device&gt; cacheLimit &lt;n&gt;
  4450. </code><br>
  4451. In asynchronous logging mode the content of cache will be written into the database and cleared if the number &lt;n&gt; datasets
  4452. in cache has reached (default: 500). Thereby the timer of asynchronous logging mode will be set new to the value of
  4453. attribute "syncInterval". <br>
  4454. </ul>
  4455. </ul>
  4456. <br>
  4457. <ul><b>colEvent</b>
  4458. <ul>
  4459. <code>
  4460. attr &lt;device&gt; colEvent &lt;n&gt;
  4461. </code><br>
  4462. The field length of database field EVENT will be adjusted. By this attribute the default value in the DbLog-device can be
  4463. adjusted if the field length in the databse was changed nanually. If colEvent=0 is set, the database field
  4464. EVENT won't be filled . <br>
  4465. <b>Note:</b> <br>
  4466. If the attribute is set, all of the field length limits are valid also for SQLite databases as noticed in Internal COLUMNS ! <br>
  4467. </ul>
  4468. </ul>
  4469. <br>
  4470. <ul><b>colReading</b>
  4471. <ul>
  4472. <code>
  4473. attr &lt;device&gt; colReading &lt;n&gt;
  4474. </code><br>
  4475. The field length of database field READING will be adjusted. By this attribute the default value in the DbLog-device can be
  4476. adjusted if the field length in the databse was changed nanually. If colReading=0 is set, the database field
  4477. READING won't be filled . <br>
  4478. <b>Note:</b> <br>
  4479. If the attribute is set, all of the field length limits are valid also for SQLite databases as noticed in Internal COLUMNS ! <br>
  4480. </ul>
  4481. </ul>
  4482. <br>
  4483. <ul><b>colValue</b>
  4484. <ul>
  4485. <code>
  4486. attr &lt;device&gt; colValue &lt;n&gt;
  4487. </code><br>
  4488. The field length of database field VALUE will be adjusted. By this attribute the default value in the DbLog-device can be
  4489. adjusted if the field length in the databse was changed nanually. If colEvent=0 is set, the database field
  4490. VALUE won't be filled . <br>
  4491. <b>Note:</b> <br>
  4492. If the attribute is set, all of the field length limits are valid also for SQLite databases as noticed in Internal COLUMNS ! <br>
  4493. </ul>
  4494. </ul>
  4495. <br>
  4496. <ul><b>DbLogType</b>
  4497. <ul>
  4498. <code>
  4499. attr &lt;device&gt; DbLogType [Current|History|Current/History]
  4500. </code><br>
  4501. This attribute determines which table or which tables in the database are wanted to use. If the attribute isn't set,
  4502. the adjustment <i>history</i> will be used as default. <br>
  4503. The meaning of the adjustments in detail are: <br><br>
  4504. <ul>
  4505. <table>
  4506. <colgroup> <col width=10%> <col width=90%> </colgroup>
  4507. <tr><td> <b>Current</b> </td><td>Events are only logged into the current-table.
  4508. The entries of current-table will evaluated with SVG-creation. </td></tr>
  4509. <tr><td> <b>History</b> </td><td>Events are only logged into the history-table. No dropdown list with proposals will created with the
  4510. SVG-creation. </td></tr>
  4511. <tr><td> <b>Current/History</b> </td><td>Events will be logged both the current- and the history-table.
  4512. The entries of current-table will evaluated with SVG-creation. </td></tr>
  4513. <tr><td> <b>SampleFill/History</b> </td><td>Events are only logged into the history-table. The entries of current-table will evaluated with SVG-creation
  4514. and can be filled up with a customizable extract of the history-table by using a
  4515. <a href="http://fhem.de/commandref.html#DbRep">DbRep-device</a> command
  4516. "set &lt;DbRep-name&gt; tableCurrentFillup" (advanced feature). </td></tr>
  4517. </table>
  4518. </ul>
  4519. <br>
  4520. <br>
  4521. <b>Note:</b> <br>
  4522. The current-table has to be used to get a Device:Reading-DropDown list when a SVG-Plot will be created. <br>
  4523. </ul>
  4524. </ul>
  4525. <br>
  4526. <ul><b>DbLogSelectionMode</b>
  4527. <ul>
  4528. <code>
  4529. attr &lt;device&gt; DbLogSelectionMode [Exclude|Include|Exclude/Include]
  4530. </code><br>
  4531. Thise DbLog-Device-Attribute specifies how the device specific Attributes DbLogExclude and DbLogInclude are handled.
  4532. If this Attribute is missing it defaults to "Exclude".
  4533. <ul>
  4534. <li>Exclude: DbLog behaves just as usual. This means everything specified in the regex in DEF will be logged by default and anything excluded
  4535. via the DbLogExclude attribute will not be logged</li>
  4536. <li>Include: Nothing will be logged, except the readings specified via regex in the DbLogInclude attribute </li>
  4537. <li>Exclude/Include: Just almost the same as Exclude, but if the reading matches the DbLogExclude attribute, then
  4538. it will further be checked against the regex in DbLogInclude whicht may possibly re-include the already
  4539. excluded reading. </li>
  4540. </ul>
  4541. </ul>
  4542. </ul>
  4543. <br>
  4544. <ul><b>DbLogInclude</b>
  4545. <ul>
  4546. <code>
  4547. attr &lt;device&gt; DbLogInclude regex:MinInterval,[regex:MinInterval] ...
  4548. </code><br>
  4549. A new Attribute DbLogInclude will be propagated
  4550. to all Devices if DBLog is used. DbLogInclude works just like DbLogExclude but
  4551. to include matching readings.
  4552. See also DbLogSelectionMode-Attribute of DbLog-Device which takes influence on
  4553. on how DbLogExclude and DbLogInclude are handled. <br>
  4554. <b>Example</b> <br>
  4555. <code>attr MyDevice1 DbLogInclude .*</code> <br>
  4556. <code>attr MyDevice2 DbLogInclude state,(floorplantext|MyUserReading):300,battery:3600</code>
  4557. </ul>
  4558. </ul>
  4559. <br>
  4560. <ul><b>DbLogExclude</b>
  4561. <ul>
  4562. <code>
  4563. attr &lt;device&gt; DbLogExclude regex:MinInterval,[regex:MinInterval] ...
  4564. </code><br>
  4565. A new Attribute DbLogExclude will be propagated to all Devices if DBLog is used.
  4566. DbLogExclude will work as regexp to exclude defined readings to log. Each individual regexp-group are separated by comma.
  4567. If a MinInterval is set, the logentry is dropped if the defined interval is not reached and value vs. lastvalue is eqal. <br>
  4568. <b>Example</b> <br>
  4569. <code>attr MyDevice1 DbLogExclude .*</code> <br>
  4570. <code>attr MyDevice2 DbLogExclude state,(floorplantext|MyUserReading):300,battery:3600</code>
  4571. </ul>
  4572. </ul>
  4573. <br>
  4574. <ul><b>excludeDevs</b>
  4575. <ul>
  4576. <code>
  4577. attr &lt;device&gt; excludeDevs &lt;devspec1&gt;,&lt;devspec2&gt;,&lt;devspec..&gt;
  4578. </code><br>
  4579. The devices "devspec1", "devspec2" up to "devspec.." will be excluded from logging into database. This attribute
  4580. will only be evaluated if internal "NOTIFYDEV" is not defined or if DbLog-define ".*:.*" (that means all devices
  4581. should be logged) is set.
  4582. Thereby devices can be explicit excluded from logging. The devices to exclude can be specified as
  4583. <a href="#devspec">device-specification</a>.
  4584. For further informations about devspec please see <a href="#devspec">device-specification</a>. <br>
  4585. <b>Example</b> <br>
  4586. <code>
  4587. attr &lt;device&gt; excludeDevs global,Log.*,Cam.*,TYPE=DbLog
  4588. </code><br>
  4589. # The devices global respectively devices starting with "Log" or "Cam" and devices with Type=DbLog
  4590. are excluded from database logging. <br>
  4591. </ul>
  4592. </ul>
  4593. <br>
  4594. <ul><b>expimpdir</b>
  4595. <ul>
  4596. <code>
  4597. attr &lt;device&gt; expimpdir &lt;directory&gt;
  4598. </code><br>
  4599. If the cache content will be exported by <a href="#DbLogsetexportCache">"exportCache"</a> or the "importCachefile"
  4600. command, the file will be written into or read from that directory. The default directory is
  4601. "(global->modpath)/log/".
  4602. Make sure the specified directory is existing and writable. <br>
  4603. <b>Example</b> <br>
  4604. <code>
  4605. attr &lt;device&gt; expimpdir /opt/fhem/cache/
  4606. </code><br>
  4607. </ul>
  4608. </ul>
  4609. <br>
  4610. <ul><b>noNotifyDev</b>
  4611. <ul>
  4612. <code>
  4613. attr &lt;device&gt; noNotifyDev [1|0]
  4614. </code><br>
  4615. Enforces that NOTIFYDEV won't set and hence won't used. <br>
  4616. </ul>
  4617. </ul>
  4618. <br>
  4619. <ul><b>noSupportPK</b>
  4620. <ul>
  4621. <code>
  4622. attr &lt;device&gt; noSupportPK [1|0]
  4623. </code><br>
  4624. Deactivates the support of a set primary key by the module.<br>
  4625. </ul>
  4626. </ul>
  4627. <br>
  4628. <ul><b>syncEvents</b>
  4629. <ul>
  4630. <code>attr &lt;device&gt; syncEvents [1|0]
  4631. </code><br>
  4632. events of reading syncEvents will be created. <br>
  4633. </ul>
  4634. </ul>
  4635. <br>
  4636. <ul><b>shutdownWait</b>
  4637. <ul>
  4638. <code>attr &lt;device&gt; shutdownWait <n>
  4639. </code><br>
  4640. causes fhem shutdown to wait n seconds for pending database commit<br/>
  4641. </ul>
  4642. </ul>
  4643. <br>
  4644. <ul><b>showproctime</b>
  4645. <ul>
  4646. <code>attr &lt;device&gt; [1|0]
  4647. </code><br>
  4648. If set, the reading "sql_processing_time" shows the required execution time (in seconds) for the sql-requests. This is not calculated
  4649. for a single sql-statement, but the summary of all sql-statements necessary for within an executed DbLog-function in background.
  4650. The reading "background_processing_time" shows the total time used in background. <br>
  4651. </ul>
  4652. </ul>
  4653. <br>
  4654. <ul><b>showNotifyTime</b>
  4655. <ul>
  4656. <code>attr &lt;device&gt; showNotifyTime [1|0]
  4657. </code><br>
  4658. If set, the reading "notify_processing_time" shows the required execution time (in seconds) in the DbLog
  4659. Notify-function. This attribute is practical for performance analyses and helps to determine the differences of time
  4660. required when the operation mode was switched from synchronous to the asynchronous mode. <br>
  4661. </ul>
  4662. </ul>
  4663. <br>
  4664. <ul><b>syncInterval</b>
  4665. <ul>
  4666. <code>attr &lt;device&gt; syncInterval &lt;n&gt;
  4667. </code><br>
  4668. If DbLog is set to asynchronous operation mode (attribute asyncMode=1), with this attribute you can setup the interval in seconds
  4669. used for storage the in memory cached events into the database. THe default value is 30 seconds. <br>
  4670. </ul>
  4671. </ul>
  4672. <br>
  4673. <ul><b>suppressAddLogV3</b>
  4674. <ul>
  4675. <code>attr &lt;device&gt; suppressAddLogV3 [1|0]
  4676. </code><br>
  4677. If set, verbose3-Logfileentries done by the addLog-function will be suppressed. <br>
  4678. </ul>
  4679. </ul>
  4680. <br>
  4681. <ul><b>suppressUndef</b>
  4682. <ul>
  4683. <code>
  4684. attr &lt;device&gt; ignoreUndef <n>
  4685. </code><br>
  4686. suppresses all undef values when returning data from the DB via get <br>
  4687. <b>Example</b> <br>
  4688. <code>#DbLog eMeter:power:::$val=($val>1500)?undef:$val</code>
  4689. </ul>
  4690. </ul>
  4691. <br>
  4692. <ul><b>timeout</b>
  4693. <ul>
  4694. <code>
  4695. attr &lt;device&gt; timeout <n>
  4696. </code><br>
  4697. setup timeout of the write cycle into database in asynchronous mode (default 86400s) <br>
  4698. </ul>
  4699. </ul>
  4700. <br>
  4701. <ul><b>valueFn</b>
  4702. <ul>
  4703. <code>
  4704. attr &lt;device&gt; valueFn {}
  4705. </code><br>
  4706. Perl expression that can use and change values of $TIMESTAMP, $DEVICE, $DEVICETYPE, $READING, $VALUE (value of reading) and
  4707. $UNIT (unit of reading value).
  4708. It also has readonly-access to $EVENT for evaluation in your expression. <br>
  4709. If $TIMESTAMP should be changed, it must meet the condition "yyyy-mm-dd hh:mm:ss", otherwise the $timestamp wouldn't
  4710. be changed.
  4711. In addition you can set the variable $IGNORE=1 if you want skip a dataset from logging. <br><br>
  4712. <b>Examples</b> <br>
  4713. <code>
  4714. attr &lt;device&gt; valueFn {if ($DEVICE eq "living_Clima" && $VALUE eq "off" ){$VALUE=0;} elsif ($DEVICE eq "e-power"){$VALUE= sprintf "%.1f", $VALUE;}}
  4715. </code> <br>
  4716. # change value "off" to "0" of device "living_Clima" and rounds value of e-power to 1f <br><br>
  4717. <code>
  4718. attr &lt;device&gt; valueFn {if ($DEVICE eq "SMA_Energymeter" && $READING eq "state"){$IGNORE=1;}}
  4719. </code><br>
  4720. # don't log the dataset of device "SMA_Energymeter" if the reading is "state" <br><br>
  4721. <code>
  4722. attr &lt;device&gt; valueFn {if ($DEVICE eq "Dum.Energy" && $READING eq "TotalConsumption"){$UNIT="W";}}
  4723. </code><br>
  4724. # set the unit of device "Dum.Energy" to "W" if reading is "TotalConsumption" <br><br>
  4725. </ul>
  4726. </ul>
  4727. <br>
  4728. <ul><b>verbose4Devs</b>
  4729. <ul>
  4730. <code>
  4731. attr &lt;device&gt; verbose4Devs &lt;device1&gt;,&lt;device2&gt;,&lt;device..&gt;
  4732. </code><br>
  4733. If verbose level 4 is used, only output of devices set in this attribute will be reported in FHEM central logfile. If this attribute
  4734. isn't set, output of all relevant devices will be reported if using verbose level 4.
  4735. The given devices are evaluated as Regex. <br>
  4736. <b>Example</b> <br>
  4737. <code>
  4738. attr &lt;device&gt; verbose4Devs sys.*,.*5000.*,Cam.*,global
  4739. </code><br>
  4740. # The devices starting with "sys", "Cam" respectively devices are containing "5000" in its name and the device "global" will be reported in FHEM
  4741. central Logfile if verbose=4 is set. <br>
  4742. </ul>
  4743. </ul>
  4744. <br>
  4745. </ul>
  4746. =end html
  4747. =begin html_DE
  4748. <a name="DbLog"></a>
  4749. <h3>DbLog</h3>
  4750. <ul>
  4751. <br>
  4752. Mit DbLog werden Events in einer Datenbank gespeichert. Es wird SQLite, MySQL/MariaDB und PostgreSQL unterstützt. <br><br>
  4753. <b>Voraussetzungen</b> <br><br>
  4754. Die Perl-Module <code>DBI</code> und <code>DBD::&lt;dbtype&gt;</code> müssen installiert werden (use <code>cpan -i &lt;module&gt;</code>
  4755. falls die eigene Distribution diese nicht schon mitbringt).
  4756. <br><br>
  4757. Auf einem Debian-System können diese Module z.Bsp. installiert werden mit: <br><br>
  4758. <ul>
  4759. <table>
  4760. <colgroup> <col width=5%> <col width=95%> </colgroup>
  4761. <tr><td> <b>DBI</b> </td><td>: <code> sudo apt-get install libdbi-perl </code> </td></tr>
  4762. <tr><td> <b>MySQL</b> </td><td>: <code> sudo apt-get install [mysql-server] mysql-client libdbd-mysql libdbd-mysql-perl </code> (mysql-server nur bei lokaler MySQL-Server-Installation) </td></tr>
  4763. <tr><td> <b>SQLite</b> </td><td>: <code> sudo apt-get install sqlite3 libdbi-perl libdbd-sqlite3-perl </code> </td></tr>
  4764. <tr><td> <b>PostgreSQL</b> </td><td>: <code> sudo apt-get install libdbd-pg-perl </code> </td></tr>
  4765. </table>
  4766. </ul>
  4767. <br>
  4768. <br>
  4769. <b>Vorbereitungen</b> <br><br>
  4770. Zunächst muss die Datenbank angelegt werden. <br>
  4771. Beispielcode bzw. Scripts zum Erstellen einer MySQL/PostgreSQL/SQLite Datenbank ist in <code>contrib/dblog/&lt;DBType&gt;_create.sql</code>
  4772. enthalten.
  4773. Die Datenbank beinhaltet 2 Tabellen: <code>current</code> und <code>history</code>. <br>
  4774. Die Tabelle <code>current</code> enthält den letzten Stand pro Device und Reading. <br>
  4775. In der Tabelle <code>history</code> sind alle Events historisch gespeichert. <br>
  4776. Beachten sie bitte unbedingt das <a href="#DbLogattr">Attribut</a> DbLogType um die Benutzung der Tabellen
  4777. <code>current</code> und <code>history</code> festzulegen.
  4778. <br><br>
  4779. Die Tabellenspalten haben folgende Bedeutung: <br><br>
  4780. <ul>
  4781. <table>
  4782. <colgroup> <col width=5%> <col width=95%> </colgroup>
  4783. <tr><td> TIMESTAMP </td><td>: Zeitpunkt des Events, z.B. <code>2007-12-30 21:45:22</code> </td></tr>
  4784. <tr><td> DEVICE </td><td>: Name des Devices, z.B. <code>Wetterstation</code> </td></tr>
  4785. <tr><td> TYPE </td><td>: Type des Devices, z.B. <code>KS300</code> </code> </td></tr>
  4786. <tr><td> EVENT </td><td>: das auftretende Event als volle Zeichenkette, z.B. <code>humidity: 71 (%)</code> </td></tr>
  4787. <tr><td> READING </td><td>: Name des Readings, ermittelt aus dem Event, z.B. <code>humidity</code> </td></tr>
  4788. <tr><td> VALUE </td><td>: aktueller Wert des Readings, ermittelt aus dem Event, z.B. <code>humidity</code> </td></tr>
  4789. <tr><td> UNIT </td><td>: Einheit, ermittelt aus dem Event, z.B. <code>%</code> </td></tr>
  4790. </table>
  4791. </ul>
  4792. <br>
  4793. <br>
  4794. <b>Index anlegen</b> <br>
  4795. Für die Leseperformance, z.B. bei der Erstellung von SVG-PLots, ist es von besonderer Bedeutung dass der <b>Index "Search_Idx"</b>
  4796. oder ein vergleichbarer Index (z.B. ein Primary Key) angelegt ist. <br><br>
  4797. Der Index "Search_Idx" kann mit diesen Statements, z.B. in der Datenbank 'fhem', angelegt werden (auch nachträglich): <br><br>
  4798. <ul>
  4799. <table>
  4800. <colgroup> <col width=5%> <col width=95%> </colgroup>
  4801. <tr><td> <b>MySQL</b> </td><td>: <code> CREATE INDEX Search_Idx ON `fhem`.`history` (DEVICE, READING, TIMESTAMP); </code> </td></tr>
  4802. <tr><td> <b>SQLite</b> </td><td>: <code> CREATE INDEX Search_Idx ON `history` (DEVICE, READING, TIMESTAMP); </code> </td></tr>
  4803. <tr><td> <b>PostgreSQL</b> </td><td>: <code> CREATE INDEX "Search_Idx" ON history USING btree (device, reading, "timestamp"); </code> </td></tr>
  4804. </table>
  4805. </ul>
  4806. <br>
  4807. Der Code zur Anlage ist ebenfalls mit in den Scripten in
  4808. <code>contrib/dblog/&lt;DBType&gt;_create.sql</code> enthalten. <br><br>
  4809. Für die Verbindung zur Datenbank wird eine <b>Konfigurationsdatei</b> verwendet.
  4810. Die Konfiguration ist in einer sparaten Datei abgelegt um das Datenbankpasswort nicht in Klartext in der
  4811. FHEM-Haupt-Konfigurationsdatei speichern zu müssen.
  4812. Ansonsten wäre es mittels des <a href="https://fhem.de/commandref_DE.html#list">list</a> Befehls einfach auslesbar.
  4813. <br><br>
  4814. Die <b>Konfigurationsdatei</b> wird z.B. nach /opt/fhem kopiert und hat folgenden Aufbau, den man an seine Umgebung
  4815. anpassen muß (entsprechende Zeilen entkommentieren und anpassen): <br><br>
  4816. <pre>
  4817. ####################################################################################
  4818. # database configuration file
  4819. #
  4820. # NOTE:
  4821. # If you don't use a value for user / password please delete the leading hash mark
  4822. # and write 'user => ""' respectively 'password => ""' instead !
  4823. #
  4824. #
  4825. ## for MySQL
  4826. ####################################################################################
  4827. #%dbconfig= (
  4828. # connection => "mysql:database=fhem;host=db;port=3306",
  4829. # user => "fhemuser",
  4830. # password => "fhempassword",
  4831. # # optional enable(1) / disable(0) UTF-8 support (at least V 4.042 is necessary)
  4832. # utf8 => 1
  4833. #);
  4834. ####################################################################################
  4835. #
  4836. ## for PostgreSQL
  4837. ####################################################################################
  4838. #%dbconfig= (
  4839. # connection => "Pg:database=fhem;host=localhost",
  4840. # user => "fhemuser",
  4841. # password => "fhempassword"
  4842. #);
  4843. ####################################################################################
  4844. #
  4845. ## for SQLite (username and password stay empty for SQLite)
  4846. ####################################################################################
  4847. #%dbconfig= (
  4848. # connection => "SQLite:dbname=/opt/fhem/fhem.db",
  4849. # user => "",
  4850. # password => ""
  4851. #);
  4852. ####################################################################################
  4853. </pre>
  4854. <br>
  4855. <a name="DbLogdefine"></a>
  4856. <b>Define</b>
  4857. <ul>
  4858. <br>
  4859. <code>define &lt;name&gt; DbLog &lt;configfilename&gt; &lt;regexp&gt;</code>
  4860. <br><br>
  4861. <code>&lt;configfilename&gt;</code> ist die vorbereitete <b>Konfigurationsdatei</b>. <br>
  4862. <code>&lt;regexp&gt;</code> ist identisch <a href="https://fhem.de/commandref_DE.html#FileLog">FileLog</a> der Filelog-Definition.
  4863. <br><br>
  4864. <b>Beispiel:</b>
  4865. <ul>
  4866. <code>define myDbLog DbLog /etc/fhem/db.conf .*:.*</code><br>
  4867. speichert alles in der Datenbank
  4868. </ul>
  4869. <br>
  4870. Nachdem das DbLog-Device definiert wurde, ist empfohlen einen <b>Konfigurationscheck</b> auszuführen: <br><br>
  4871. <ul>
  4872. <code>set &lt;name&gt; configCheck</code> <br>
  4873. </ul>
  4874. <br>
  4875. Dieser Check prüft einige wichtige Einstellungen des DbLog-Devices und gibt Empfehlungen für potentielle Verbesserungen.
  4876. (verfügbar für MySQL, PostgreSQL) <br><br>
  4877. <br>
  4878. DbLog unterscheidet den synchronen (Default) und asynchronen Logmodus. Der Logmodus ist über das
  4879. <a href="#DbLogattr">Attribut</a> asyncMode einstellbar. Ab Version 2.13.5 unterstützt DbLog einen gesetzten
  4880. Primary Key (PK) in den Tabellen Current und History. Soll PostgreSQL mit PK genutzt werden, muss PostgreSQL mindestens
  4881. Version 9.5 sein.
  4882. <br><br>
  4883. Der gespeicherte Wert des Readings wird optimiert für eine automatisierte Nachverarbeitung, z.B. <code>yes</code> wird transformiert
  4884. nach <code>1</code>. <br><br>
  4885. Die gespeicherten Werte können mittels GET Funktion angezeigt werden:
  4886. <ul>
  4887. <code>get myDbLog - - 2012-11-10 2012-11-10 KS300:temperature</code>
  4888. </ul>
  4889. <br>
  4890. <b>FileLog-Dateien nach DbLog übertragen</b> <br><br>
  4891. Zur Übertragung von vorhandenen Filelog-Daten in die DbLog-Datenbank steht das spezielle Modul 98_FileLogConvert.pm
  4892. zur Verfügung. <br>
  4893. Dieses Modul kann <a href="https://svn.fhem.de/trac/browser/trunk/fhem/contrib/98_FileLogConvert.pm"> hier</a>
  4894. bzw. aus dem Verzeichnis ./contrib geladen werden.
  4895. Weitere Informationen und Hilfestellung gibt es im entsprechenden <a href="https://forum.fhem.de/index.php/topic,66383.0.html">
  4896. Forumthread </a>. <br><br><br>
  4897. <b>Reporting und Management von DbLog-Datenbankinhalten</b> <br><br>
  4898. Mit Hilfe <a href="https://fhem.de/commandref_DE.html#SVG">SVG</a> können Datenbankinhalte visualisiert werden. <br>
  4899. Darüber hinaus kann das Modul <a href="https://fhem.de/commandref_DE.html#DbRep">DbRep</a> genutzt werden um tabellarische
  4900. Datenbankauswertungen anzufertigen oder den Datenbankinhalt mit den zur Verfügung stehenden Funktionen zu verwalten.
  4901. <br><br>
  4902. </ul>
  4903. <br>
  4904. <br>
  4905. <a name="DbLogset"></a>
  4906. <b>Set</b>
  4907. <ul>
  4908. <code>set &lt;name&gt; addLog &lt;devspec&gt;:&lt;Reading&gt; [Value] </code><br><br>
  4909. <ul> Fügt einen zusatzlichen Logeintrag einer Device/Reading-Kombination in die Datenbank ein.
  4910. Optional kann "Value" für den Readingwert angegeben werden. Ist Value nicht angegeben, wird der aktuelle
  4911. Wert des Readings in die DB eingefügt. Das Feld "$EVENT" wird automatisch mit "addLog" belegt. Das Device kann
  4912. als <a href="#devspec">Geräte-Spezifikation</a> angegeben werden. "Reading" wird als regulärer Ausdruck ausgewertet.
  4913. Es wird KEIN zusätzlicher Event im System erzeugt !<br><br>
  4914. <b>Beispiele:</b> <br>
  4915. set &lt;name&gt; addLog SMA_Energymeter:Bezug_Wirkleistung <br>
  4916. set &lt;name&gt; addLog TYPE=SSCam:state <br>
  4917. set &lt;name&gt; addLog MyWetter:(fc10.*|fc8.*) <br>
  4918. set &lt;name&gt; addLog MyWetter:(wind|wind_ch.*) 20 <br>
  4919. set &lt;name&gt; addLog TYPE=CUL_HM:FILTER=model=HM-CC-RT-DN:FILTER=subType!=(virtual|):(measured-temp|desired-temp|actuator) <br>
  4920. </ul><br>
  4921. <code>set &lt;name&gt; clearReadings </code><br><br>
  4922. <ul> Leert Readings die von verschiedenen DbLog-Funktionen angelegt wurden. </ul><br>
  4923. <code>set &lt;name&gt; eraseReadings </code><br><br>
  4924. <ul> Löscht alle Readings außer dem Reading "state". </ul><br>
  4925. <code>set &lt;name&gt; commitCache </code><br><br>
  4926. <ul>Im asynchronen Modus (<a href="#DbLogattr">Attribut</a> asyncMode=1), werden die im Speicher gecachten Daten in die Datenbank geschrieben
  4927. und danach der Cache geleert. Der interne Timer des asynchronen Modus wird dabei neu gesetzt.
  4928. Der Befehl kann nützlich sein um manuell oder z.B. über ein AT den Cacheinhalt zu einem definierten Zeitpunkt in die
  4929. Datenbank zu schreiben. </ul><br>
  4930. <code>set &lt;name&gt; configCheck </code><br><br>
  4931. <ul>Es werden einige wichtige Einstellungen geprüft und Empfehlungen zurück gegeben wenn potentielle Verbesserungen
  4932. identifiziert wurden. (Verfügbar für MySQL, PostgreSQL) </ul><br/>
  4933. <code>set &lt;name&gt; count </code><br><br>
  4934. <ul>Zählt die Datensätze in den Tabellen current und history und schreibt die Ergebnisse in die Readings
  4935. countCurrent und countHistory.</ul><br>
  4936. <code>set &lt;name&gt; countNbl </code><br/><br>
  4937. <ul>Die non-blocking Ausführung von "set &lt;name&gt; count".</ul><br>
  4938. <code>set &lt;name&gt; deleteOldDays &lt;n&gt;</code><br/><br>
  4939. <ul>Löscht Datensätze in Tabelle history, die älter sind als &lt;n&gt; Tage sind.
  4940. Die Anzahl der gelöschten Datens&auml;tze wird in das Reading lastRowsDeleted geschrieben.</ul><br>
  4941. <code>set &lt;name&gt; deleteOldDaysNbl &lt;n&gt;</code><br><br>
  4942. <ul>Identisch zu Funktion "deleteOldDays" wobei deleteOldDaysNbl nicht blockierend ausgeführt wird. </ul><br>
  4943. <a name="DbLogsetexportCache"></a>
  4944. <code>set &lt;name&gt; exportCache [nopurge | purgeCache] </code><br><br>
  4945. <ul>Wenn DbLog im asynchronen Modus betrieben wird, kann der Cache mit diesem Befehl in ein Textfile geschrieben
  4946. werden. Das File wird per Default in dem Verzeichnis (global->modpath)/log/ erstellt. Das Zielverzeichnis kann mit
  4947. dem <a href="#DbLogattr">Attribut</a> expimpdir geändert werden. <br>
  4948. Der Name des Files wird automatisch generiert und enthält den Präfix "cache_", gefolgt von dem DbLog-Devicenamen und
  4949. dem aktuellen Zeitstempel, z.B. "cache_LogDB_2017-03-23_22-13-55". <br>
  4950. Mit den Optionen "nopurge" bzw. "purgeCache" wird festgelegt, ob der Cacheinhalt nach dem Export gelöscht werden
  4951. soll oder nicht. Mit "nopurge" (default) bleibt der Cacheinhalt erhalten. </ul><br>
  4952. <code>set &lt;name&gt; importCachefile &lt;file&gt; </code><br><br>
  4953. <ul>Importiert ein mit "exportCache" geschriebenes File in die Datenbank.
  4954. Die verfügbaren Dateien werden per Default im Verzeichnis (global->modpath)/log/ gesucht und eine Drop-Down Liste
  4955. erzeugt sofern Dateien gefunden werden. Das Quellverzeichnis kann mit dem <a href="#DbLogattr">Attribut</a> expimpdir geändert werden. <br>
  4956. Es werden nur die Dateien angezeigt, die dem Muster "cache_", gefolgt von dem DbLog-Devicenamen entsprechen. <br>
  4957. Zum Beispiel "cache_LogDB_2017-03-23_22-13-55", falls das Log-Device "LogDB" heißt. <br>
  4958. Nach einem erfolgreichen Import wird das File mit dem Präfix "impdone_" versehen und erscheint dann nicht mehr
  4959. in der Drop-Down Liste. Soll ein Cachefile in eine andere als der Quelldatenbank importiert werden, kann das
  4960. DbLog-Device im Filenamen angepasst werden damit dieses File den Suchktiterien entspricht und in der Drop-Down Liste
  4961. erscheint. </ul><br>
  4962. <code>set &lt;name&gt; listCache </code><br><br>
  4963. <ul>Wenn DbLog im asynchronen Modus betrieben wird (Attribut asyncMode=1), können mit diesem Befehl die im Speicher gecachten Events
  4964. angezeigt werden.</ul><br>
  4965. <code>set &lt;name&gt; purgeCache </code><br><br>
  4966. <ul>Im asynchronen Modus (<a href="#DbLogattr">Attribut</a> asyncMode=1), werden die im Speicher gecachten Daten gelöscht.
  4967. Es werden keine Daten aus dem Cache in die Datenbank geschrieben. </ul><br>
  4968. <code>set &lt;name&gt; reduceLog &lt;n&gt; [average[=day]] [exclude=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...]</code><br><br>
  4969. <ul>Reduziert historische Datensaetze, die älter sind als &lt;n&gt; Tage auf einen Eintrag pro Stunde (den ersten) je Device & Reading.<br><br>
  4970. <b>ACHTUNG:</b> Es wird dringend empfohlen zu überprüfen ob der standard INDEX 'Search_Idx' in der Tabelle 'history' existiert! <br>
  4971. Die Abarbeitung dieses Befehls dauert unter Umständen (ohne INDEX) extrem lange. FHEM wird durch den Befehl bis
  4972. zur Fertigstellung <b>komplett blockiert !</b> <br><br>
  4973. Das Reading "reduceLogState" zeigt den Ausführungsstatus des letzten reduceLog-Befehls. <br><br>
  4974. Durch die optionale Angabe von 'average' wird nicht nur die Datenbank bereinigt, sondern alle numerischen Werte
  4975. einer Stunde werden auf einen einzigen Mittelwert reduziert. <br>
  4976. Durch die optionale Angabe von 'average=day' wird nicht nur die Datenbank bereinigt, sondern alle numerischen
  4977. Werte eines Tages auf einen einzigen Mittelwert reduziert. (impliziert 'average') <br>
  4978. Optional kann als letzer Parameter "EXCLUDE=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...."
  4979. angegeben werden um device/reading Kombinationen von reduceLog auszuschließen. <br>
  4980. Optional kann als letzer Parameter "INCLUDE=Database-deviceRegExp:Database-ReadingRegExp" angegeben werden um
  4981. die auf die Datenbank ausgeführte SELECT-Abfrage einzugrenzen, was die RAM-Belastung verringert und die
  4982. Performance erhöht. (Wildcards sind % und _) <br>
  4983. </ul><br>
  4984. <code>set &lt;name&gt; reduceLogNbl &lt;n&gt; [average[=day]] [exclude=deviceRegExp1:ReadingRegExp1,deviceRegExp2:ReadingRegExp2,...]</code><br><br>
  4985. <ul>
  4986. Führt die gleiche Funktion wie "set &lt;name&gt; reduceLog" aus. Im Gegensatz zu reduceLog wird mit FHEM wird durch den Befehl reduceLogNbl nicht
  4987. mehr blockiert da diese Funktion non-blocking implementiert ist ! <br>
  4988. </ul><br>
  4989. <code>set &lt;name&gt; reopen [n]</code><br/><br/>
  4990. <ul>Schließt die Datenbank und öffnet sie danach sofort wieder wenn keine Zeit [n] in Sekunden angegeben wurde.
  4991. Dabei wird die Journaldatei geleert und neu angelegt.<br/>
  4992. Verbessert den Datendurchsatz und vermeidet Speicherplatzprobleme. <br>
  4993. Wurde eine optionale Verzögerungszeit [n] in Sekunden angegeben, wird die Verbindung zur Datenbank geschlossen und erst
  4994. nach Ablauf von [n] Sekunden wieder neu verbunden.
  4995. Im synchronen Modus werden die Events in dieser Zeit nicht gespeichert.
  4996. Im asynchronen Modus werden die Events im Cache gespeichert und nach dem Reconnect in die Datenbank geschrieben. </ul><br>
  4997. <code>set &lt;name&gt; rereadcfg </code><br/><br/>
  4998. <ul>Schließt die Datenbank und öffnet sie danach sofort wieder. Dabei wird die Journaldatei geleert und neu angelegt.<br/>
  4999. Verbessert den Datendurchsatz und vermeidet Speicherplatzprobleme.<br/>
  5000. Zwischen dem Schließen der Verbindung und dem Neuverbinden werden die Konfigurationsdaten neu gelesen</ul><br/>
  5001. <code>set &lt;name&gt; userCommand &lt;validSqlStatement&gt;</code><br/><br/>
  5002. <ul><b>BENUTZE DIESE FUNKTION NUR, WENN DU WIRKLICH (WIRKLICH!) WEISST, WAS DU TUST!!!</b><br/><br/>
  5003. Führt einen beliebigen (!!!) sql Befehl in der Datenbank aus. Der Befehl und ein zurückgeliefertes
  5004. Ergebnis wird in das Reading "userCommand" bzw. "userCommandResult" geschrieben. Das Ergebnis kann nur
  5005. einzeilig sein.
  5006. Für SQL-Statements, die mehrzeilige Ergebnisse liefern, kann das Auswertungsmodul
  5007. <a href=https://fhem.de/commandref_DE.html#DbRep>DbRep</a> genutzt werden.</br>
  5008. Wird von der Datenbankschnittstelle kein Ergebnis (undef) zurückgeliefert, erscheint die Meldung "no result"
  5009. im Reading "userCommandResult".
  5010. </ul><br>
  5011. </ul><br>
  5012. <a name="DbLogget"></a>
  5013. <b>Get</b>
  5014. <ul>
  5015. <code>get &lt;name&gt; ReadingsVal&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;device&gt; &lt;reading&gt; &lt;default&gt;</code><br/>
  5016. <code>get &lt;name&gt; ReadingsTimestamp &lt;device&gt; &lt;reading&gt; &lt;default&gt;</code><br/>
  5017. <br/>
  5018. Liest einen einzelnen Wert aus der Datenbank, Benutzung und Syntax sind weitgehend identisch zu ReadingsVal() und ReadingsTimestamp().<br/>
  5019. </ul>
  5020. <br/>
  5021. <br/>
  5022. <ul>
  5023. <code>get &lt;name&gt; &lt;infile&gt; &lt;outfile&gt; &lt;from&gt;
  5024. &lt;to&gt; &lt;column_spec&gt; </code>
  5025. <br><br>
  5026. Liesst Daten aus der Datenbank. Wird durch die Frontends benutzt um Plots
  5027. zu generieren ohne selbst auf die Datenank zugreifen zu müssen.
  5028. <br>
  5029. <ul>
  5030. <li>&lt;in&gt;<br>
  5031. Ein Parameter um eine Kompatibilität zum Filelog herzustellen.
  5032. Dieser Parameter ist per default immer auf <code>-</code> zu setzen.<br>
  5033. Folgende Ausprägungen sind zugelassen:<br>
  5034. <ul>
  5035. <li>current: die aktuellen Werte aus der Tabelle "current" werden gelesen.</li>
  5036. <li>history: die historischen Werte aus der Tabelle "history" werden gelesen.</li>
  5037. <li>-: identisch wie "history"</li>
  5038. </ul>
  5039. </li>
  5040. <li>&lt;out&gt;<br>
  5041. Ein Parameter um eine Kompatibilität zum Filelog herzustellen.
  5042. Dieser Parameter ist per default immer auf <code>-</code> zu setzen um die
  5043. Ermittlung der Daten aus der Datenbank für die Plotgenerierung zu prüfen.<br>
  5044. Folgende Ausprägungen sind zugelassen:<br>
  5045. <ul>
  5046. <li>ALL: Es werden alle Spalten der Datenbank ausgegeben. Inclusive einer Überschrift.</li>
  5047. <li>Array: Es werden alle Spalten der Datenbank als Hash ausgegeben. Alle Datensätze als Array zusammengefasst.</li>
  5048. <li>INT: intern zur Plotgenerierung verwendet</li>
  5049. <li>-: default</li>
  5050. </ul>
  5051. </li>
  5052. <li>&lt;from&gt; / &lt;to&gt;<br>
  5053. Wird benutzt um den Zeitraum der Daten einzugrenzen. Es ist das folgende
  5054. Zeitformat oder ein Teilstring davon zu benutzen:<br>
  5055. <ul><code>YYYY-MM-DD_HH24:MI:SS</code></ul></li>
  5056. <li>&lt;column_spec&gt;<br>
  5057. Für jede column_spec Gruppe wird ein Datenset zurückgegeben welches
  5058. durch einen Kommentar getrennt wird. Dieser Kommentar repräsentiert
  5059. die column_spec.<br>
  5060. Syntax: &lt;device&gt;:&lt;reading&gt;:&lt;default&gt;:&lt;fn&gt;:&lt;regexp&gt;<br>
  5061. <ul>
  5062. <li>&lt;device&gt;<br>
  5063. Der Name des Devices. Achtung: Gross/Kleinschreibung beachten!<br>
  5064. Es kann ein % als Jokerzeichen angegeben werden.</li>
  5065. <li>&lt;reading&gt;<br>
  5066. Das Reading des angegebenen Devices zur Datenselektion.<br>
  5067. Es kann ein % als Jokerzeichen angegeben werden.<br>
  5068. Achtung: Gross/Kleinschreibung beachten!
  5069. </li>
  5070. <li>&lt;default&gt;<br>
  5071. Zur Zeit noch nicht implementiert.
  5072. </li>
  5073. <li>&lt;fn&gt;
  5074. Angabe einer speziellen Funktion:
  5075. <ul>
  5076. <li>int<br>
  5077. Ermittelt den Zahlenwert ab dem Anfang der Zeichenkette aus der
  5078. Spalte "VALUE". Benutzt z.B. für Ausprägungen wie 10%.
  5079. </li>
  5080. <li>int&lt;digit&gt;<br>
  5081. Ermittelt den Zahlenwert ab dem Anfang der Zeichenkette aus der
  5082. Spalte "VALUE", inclusive negativen Vorzeichen und Dezimaltrenner.
  5083. Benutzt z.B. für Auspägungen wie -5.7&deg;C.
  5084. </li>
  5085. <li>delta-h / delta-d<br>
  5086. Ermittelt die relative Veränderung eines Zahlenwertes pro Stunde
  5087. oder pro Tag. Wird benutzt z.B. für Spalten die einen
  5088. hochlaufenden Zähler enthalten wie im Falle für ein KS300 Regenzähler
  5089. oder dem 1-wire Modul OWCOUNT.
  5090. </li>
  5091. <li>delta-ts<br>
  5092. Ermittelt die vergangene Zeit zwischen dem letzten und dem aktuellen Logeintrag
  5093. in Sekunden und ersetzt damit den originalen Wert.
  5094. </li>
  5095. </ul></li>
  5096. <li>&lt;regexp&gt;<br>
  5097. Diese Zeichenkette wird als Perl Befehl ausgewertet.
  5098. Die regexp wird vor dem angegebenen &lt;fn&gt; Parameter ausgeführt.
  5099. <br>
  5100. Bitte zur Beachtung: Diese Zeichenkette darf keine Leerzeichen
  5101. enthalten da diese sonst als &lt;column_spec&gt; Trennung
  5102. interpretiert werden und alles nach dem Leerzeichen als neue
  5103. &lt;column_spec&gt; gesehen wird.<br>
  5104. <b>Schlüsselwörter</b>
  5105. <li>$val ist der aktuelle Wert die die Datenbank für ein Device/Reading ausgibt.</li>
  5106. <li>$ts ist der aktuelle Timestamp des Logeintrages.</li>
  5107. <li>Wird als $val das Schlüsselwort "hide" zurückgegeben, so wird dieser Logeintrag nicht
  5108. ausgegeben, trotzdem aber für die Zeitraumberechnung verwendet.</li>
  5109. <li>Wird als $val das Schlüsselwort "ignore" zurückgegeben, so wird dieser Logeintrag
  5110. nicht für eine Folgeberechnung verwendet.</li>
  5111. </li>
  5112. </ul></li>
  5113. </ul>
  5114. <br><br>
  5115. <b>Beispiele:</b>
  5116. <ul>
  5117. <li><code>get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature</code></li>
  5118. <li><code>get myDbLog current ALL - - %:temperature</code></li><br>
  5119. Damit erhält man alle aktuellen Readings "temperature" von allen in der DB geloggten Devices.
  5120. Achtung: bei Nutzung von Jokerzeichen auf die history-Tabelle kann man sein FHEM aufgrund langer Laufzeit lahmlegen!
  5121. <li><code>get myDbLog - - 2012-11-10_10 2012-11-10_20 KS300:temperature::int1</code><br>
  5122. gibt Daten aus von 10Uhr bis 20Uhr am 10.11.2012</li>
  5123. <li><code>get myDbLog - all 2012-11-10 2012-11-20 KS300:temperature</code></li>
  5124. <li><code>get myDbLog - - 2012-11-10 2012-11-20 KS300:temperature KS300:rain::delta-h KS300:rain::delta-d</code></li>
  5125. <li><code>get myDbLog - - 2012-11-10 2012-11-20 MyFS20:data:::$val=~s/(on|off).*/$1eq"on"?1:0/eg</code><br>
  5126. gibt 1 zurück für alle Ausprägungen von on* (on|on-for-timer etc) und 0 für alle off*</li>
  5127. <li><code>get myDbLog - - 2012-11-10 2012-11-20 Bodenfeuchte:data:::$val=~s/.*B:\s([-\.\d]+).*/$1/eg</code><br>
  5128. Beispiel von OWAD: Ein Wert wie z.B.: <code>"A: 49.527 % B: 66.647 % C: 9.797 % D: 0.097 V"</code><br>
  5129. und die Ausgabe ist für das Reading B folgende: <code>2012-11-20_10:23:54 66.647</code></li>
  5130. <li><code>get DbLog - - 2013-05-26 2013-05-28 Pumpe:data::delta-ts:$val=~s/on/hide/</code><br>
  5131. Realisierung eines Betriebsstundenzählers. Durch delta-ts wird die Zeit in Sek zwischen den Log-
  5132. Einträgen ermittelt. Die Zeiten werden bei den on-Meldungen nicht ausgegeben welche einer Abschaltzeit
  5133. entsprechen würden.</li>
  5134. </ul>
  5135. <br><br>
  5136. </ul>
  5137. <b>Get</b> für die Nutzung von webcharts
  5138. <ul>
  5139. <code>get &lt;name&gt; &lt;infile&gt; &lt;outfile&gt; &lt;from&gt;
  5140. &lt;to&gt; &lt;device&gt; &lt;querytype&gt; &lt;xaxis&gt; &lt;yaxis&gt; &lt;savename&gt; </code>
  5141. <br><br>
  5142. Liest Daten aus der Datenbank aus und gibt diese in JSON formatiert aus. Wird für das Charting Frontend genutzt
  5143. <br>
  5144. <ul>
  5145. <li>&lt;name&gt;<br>
  5146. Der Name des definierten DbLogs, so wie er in der fhem.cfg angegeben wurde.</li>
  5147. <li>&lt;in&gt;<br>
  5148. Ein Dummy Parameter um eine Kompatibilität zum Filelog herzustellen.
  5149. Dieser Parameter ist immer auf <code>-</code> zu setzen.</li>
  5150. <li>&lt;out&gt;<br>
  5151. Ein Dummy Parameter um eine Kompatibilität zum Filelog herzustellen.
  5152. Dieser Parameter ist auf <code>webchart</code> zu setzen um die Charting Get Funktion zu nutzen.
  5153. </li>
  5154. <li>&lt;from&gt; / &lt;to&gt;<br>
  5155. Wird benutzt um den Zeitraum der Daten einzugrenzen. Es ist das folgende
  5156. Zeitformat zu benutzen:<br>
  5157. <ul><code>YYYY-MM-DD_HH24:MI:SS</code></ul></li>
  5158. <li>&lt;device&gt;<br>
  5159. Ein String, der das abzufragende Device darstellt.</li>
  5160. <li>&lt;querytype&gt;<br>
  5161. Ein String, der die zu verwendende Abfragemethode darstellt. Zur Zeit unterstützte Werte sind: <br>
  5162. <code>getreadings</code> um für ein bestimmtes device alle Readings zu erhalten<br>
  5163. <code>getdevices</code> um alle verfügbaren devices zu erhalten<br>
  5164. <code>timerange</code> um Chart-Daten abzufragen. Es werden die Parameter 'xaxis', 'yaxis', 'device', 'to' und 'from' benötigt<br>
  5165. <code>savechart</code> um einen Chart unter Angabe eines 'savename' und seiner zugehörigen Konfiguration abzuspeichern<br>
  5166. <code>deletechart</code> um einen zuvor gespeicherten Chart unter Angabe einer id zu löschen<br>
  5167. <code>getcharts</code> um eine Liste aller gespeicherten Charts zu bekommen.<br>
  5168. <code>getTableData</code> um Daten aus der Datenbank abzufragen und in einer Tabelle darzustellen. Benötigt paging Parameter wie start und limit.<br>
  5169. <code>hourstats</code> um Statistiken für einen Wert (yaxis) für eine Stunde abzufragen.<br>
  5170. <code>daystats</code> um Statistiken für einen Wert (yaxis) für einen Tag abzufragen.<br>
  5171. <code>weekstats</code> um Statistiken für einen Wert (yaxis) für eine Woche abzufragen.<br>
  5172. <code>monthstats</code> um Statistiken für einen Wert (yaxis) für einen Monat abzufragen.<br>
  5173. <code>yearstats</code> um Statistiken für einen Wert (yaxis) für ein Jahr abzufragen.<br>
  5174. </li>
  5175. <li>&lt;xaxis&gt;<br>
  5176. Ein String, der die X-Achse repräsentiert</li>
  5177. <li>&lt;yaxis&gt;<br>
  5178. Ein String, der die Y-Achse repräsentiert</li>
  5179. <li>&lt;savename&gt;<br>
  5180. Ein String, unter dem ein Chart in der Datenbank gespeichert werden soll</li>
  5181. <li>&lt;chartconfig&gt;<br>
  5182. Ein jsonstring der den zu speichernden Chart repräsentiert</li>
  5183. <li>&lt;pagingstart&gt;<br>
  5184. Ein Integer um den Startwert für die Abfrage 'getTableData' festzulegen</li>
  5185. <li>&lt;paginglimit&gt;<br>
  5186. Ein Integer um den Limitwert für die Abfrage 'getTableData' festzulegen</li>
  5187. </ul>
  5188. <br><br>
  5189. Beispiele:
  5190. <ul>
  5191. <li><code>get logdb - webchart "" "" "" getcharts</code><br>
  5192. Liefert alle gespeicherten Charts aus der Datenbank</li>
  5193. <li><code>get logdb - webchart "" "" "" getdevices</code><br>
  5194. Liefert alle verfügbaren Devices aus der Datenbank</li>
  5195. <li><code>get logdb - webchart "" "" ESA2000_LED_011e getreadings</code><br>
  5196. Liefert alle verfügbaren Readings aus der Datenbank unter Angabe eines Gerätes</li>
  5197. <li><code>get logdb - webchart 2013-02-11_00:00:00 2013-02-12_00:00:00 ESA2000_LED_011e timerange TIMESTAMP day_kwh</code><br>
  5198. Liefert Chart-Daten, die auf folgenden Parametern basieren: 'xaxis', 'yaxis', 'device', 'to' und 'from'<br>
  5199. Die Ausgabe erfolgt als JSON, z.B.: <code>[{'TIMESTAMP':'2013-02-11 00:10:10','VALUE':'0.22431388090756'},{'TIMESTAMP'.....}]</code></li>
  5200. <li><code>get logdb - webchart 2013-02-11_00:00:00 2013-02-12_00:00:00 ESA2000_LED_011e savechart TIMESTAMP day_kwh tageskwh</code><br>
  5201. Speichert einen Chart unter Angabe eines 'savename' und seiner zugehörigen Konfiguration</li>
  5202. <li><code>get logdb - webchart "" "" "" deletechart "" "" 7</code><br>
  5203. Löscht einen zuvor gespeicherten Chart unter Angabe einer id</li>
  5204. </ul>
  5205. <br><br>
  5206. </ul>
  5207. <a name="DbLogattr"></a>
  5208. <b>Attribute</b>
  5209. <br><br>
  5210. <ul><b>addStateEvent</b>
  5211. <ul>
  5212. <code>attr &lt;device&gt; addStateEvent [0|1]
  5213. </code><br>
  5214. Bekanntlich wird normalerweise bei einem Event mit dem Reading "state" der state-String entfernt, d.h.
  5215. der Event ist nicht zum Beispiel "state: on" sondern nur "on". <br>
  5216. Meistens ist es aber hilfreich in DbLog den kompletten Event verarbeiten zu können. Deswegen übernimmt DbLog per Default
  5217. den Event inklusive dem Reading-String "state". <br>
  5218. In einigen Fällen, z.B. alten oder speziellen Modulen, ist es allerdings wünschenswert den state-String wie gewöhnlich
  5219. zu entfernen. In diesen Fällen bitte addStateEvent = "0" setzen.
  5220. Versuchen sie bitte diese Einstellung, falls es mit dem Standard Probleme geben sollte.
  5221. <br>
  5222. </ul>
  5223. </ul>
  5224. <br>
  5225. <ul><b>asyncMode</b>
  5226. <ul>
  5227. <code>attr &lt;device&gt; asyncMode [1|0]
  5228. </code><br>
  5229. Dieses Attribut stellt den Arbeitsmodus von DbLog ein. Im asynchronen Modus (asyncMode=1), werden die zu speichernden Events zunächst in Speicher
  5230. gecacht. Nach Ablauf der Synchronisationszeit (Attribut syncInterval) oder bei Erreichen der maximalen Anzahl der Datensätze im Cache
  5231. (Attribut cacheLimit) werden die gecachten Events im Block in die Datenbank geschrieben.
  5232. Ist die Datenbank nicht verfügbar, werden die Events weiterhin im Speicher gehalten und nach Ablauf des Syncintervalls in die Datenbank
  5233. geschrieben falls sie dann verfügbar ist. <br>
  5234. Im asynchronen Mode werden die Daten nicht blockierend mit einem separaten Hintergrundprozess in die Datenbank geschrieben.
  5235. Det Timeout-Wert für diesen Hintergrundprozess kann mit dem Attribut "timeout" (Default 86400s) eingestellt werden.
  5236. Im synchronen Modus (Normalmodus) werden die Events nicht gecacht und sofort in die Datenbank geschrieben. Ist die Datenbank nicht
  5237. verfügbar gehen sie verloren.<br>
  5238. </ul>
  5239. </ul>
  5240. <br>
  5241. <ul><b>cacheEvents</b>
  5242. <ul>
  5243. <code>attr &lt;device&gt; cacheEvents [2|1|0]
  5244. </code><br>
  5245. <ul>
  5246. <li>cacheEvents=1: es werden Events für das Reading CacheUsage erzeugt wenn ein Event zum Cache hinzugefügt wurde. </li>
  5247. <li>cacheEvents=2: es werden Events für das Reading CacheUsage erzeugt wenn im asynchronen Mode der Schreibzyklus in die
  5248. Datenbank beginnt. CacheUsage enthält zu diesem Zeitpunkt die Anzahl der in die Datenbank zu schreibenden
  5249. Datensätze. </li><br>
  5250. </ul>
  5251. </ul>
  5252. </ul>
  5253. <br>
  5254. <ul><b>cacheLimit</b>
  5255. <ul>
  5256. <code>
  5257. attr &lt;device&gt; cacheLimit &lt;n&gt;
  5258. </code><br>
  5259. Im asynchronen Logmodus wird der Cache in die Datenbank weggeschrieben und geleert wenn die Anzahl &lt;n&gt; Datensätze
  5260. im Cache erreicht ist (Default: 500). Der Timer des asynchronen Logmodus wird dabei neu auf den Wert des Attributs "syncInterval"
  5261. gesetzt. <br>
  5262. </ul>
  5263. </ul>
  5264. <br>
  5265. <ul><b>colEvent</b>
  5266. <ul>
  5267. <code>
  5268. attr &lt;device&gt; colEvent &lt;n&gt;
  5269. </code><br>
  5270. Die Feldlänge für das DB-Feld EVENT wird userspezifisch angepasst. Mit dem Attribut kann der Default-Wert im Modul
  5271. verändert werden wenn die Feldlänge in der Datenbank manuell geändert wurde. Mit colEvent=0 wird das Datenbankfeld
  5272. EVENT nicht gefüllt. <br>
  5273. <b>Hinweis:</b> <br>
  5274. Mit gesetztem Attribut gelten alle Feldlängenbegrenzungen auch für SQLite DB wie im Internal COLUMNS angezeigt ! <br>
  5275. </ul>
  5276. </ul>
  5277. <br>
  5278. <ul><b>colReading</b>
  5279. <ul>
  5280. <code>
  5281. attr &lt;device&gt; colReading &lt;n&gt;
  5282. </code><br>
  5283. Die Feldlänge für das DB-Feld READING wird userspezifisch angepasst. Mit dem Attribut kann der Default-Wert im Modul
  5284. verändert werden wenn die Feldlänge in der Datenbank manuell geändert wurde. Mit colReading=0 wird das Datenbankfeld
  5285. READING nicht gefüllt. <br>
  5286. <b>Hinweis:</b> <br>
  5287. Mit gesetztem Attribut gelten alle Feldlängenbegrenzungen auch für SQLite DB wie im Internal COLUMNS angezeigt ! <br>
  5288. </ul>
  5289. </ul>
  5290. <br>
  5291. <ul><b>colValue</b>
  5292. <ul>
  5293. <code>
  5294. attr &lt;device&gt; colValue &lt;n&gt;
  5295. </code><br>
  5296. Die Feldlänge für das DB-Feld VALUE wird userspezifisch angepasst. Mit dem Attribut kann der Default-Wert im Modul
  5297. verändert werden wenn die Feldlänge in der Datenbank manuell geändert wurde. Mit colValue=0 wird das Datenbankfeld
  5298. VALUE nicht gefüllt. <br>
  5299. <b>Hinweis:</b> <br>
  5300. Mit gesetztem Attribut gelten alle Feldlängenbegrenzungen auch für SQLite DB wie im Internal COLUMNS angezeigt ! <br>
  5301. </ul>
  5302. </ul>
  5303. <br>
  5304. <ul><b>DbLogType</b>
  5305. <ul>
  5306. <code>
  5307. attr &lt;device&gt; DbLogType [Current|History|Current/History|SampleFill/History]
  5308. </code><br>
  5309. Dieses Attribut legt fest, welche Tabelle oder Tabellen in der Datenbank genutzt werden sollen. Ist dieses Attribut nicht gesetzt, wird
  5310. per default die Einstellung <i>history</i> verwendet. <br><br>
  5311. Bedeutung der Einstellungen sind: <br><br>
  5312. <ul>
  5313. <table>
  5314. <colgroup> <col width=10%> <col width=90%> </colgroup>
  5315. <tr><td> <b>Current</b> </td><td>Events werden nur in die current-Tabelle geloggt.
  5316. Die current-Tabelle wird bei der SVG-Erstellung ausgewertet. </td></tr>
  5317. <tr><td> <b>History</b> </td><td>Events werden nur in die history-Tabelle geloggt. Es wird keine DropDown-Liste mit Vorschlägen bei der SVG-Erstellung
  5318. erzeugt. </td></tr>
  5319. <tr><td> <b>Current/History</b> </td><td>Events werden sowohl in die current- also auch in die hitory Tabelle geloggt.
  5320. Die current-Tabelle wird bei der SVG-Erstellung ausgewertet.</td></tr>
  5321. <tr><td> <b>SampleFill/History</b> </td><td>Events werden nur in die history-Tabelle geloggt. Die current-Tabelle wird bei der SVG-Erstellung ausgewertet und
  5322. kann zur Erzeugung einer DropDown-Liste mittels einem
  5323. <a href="http://fhem.de/commandref_DE.html#DbRep">DbRep-Device</a> <br> "set &lt;DbRep-Name&gt; tableCurrentFillup" mit
  5324. einem einstellbaren Extract der history-Tabelle gefüllt werden (advanced Feature). </td></tr>
  5325. </table>
  5326. </ul>
  5327. <br>
  5328. <br>
  5329. <b>Hinweis:</b> <br>
  5330. Die Current-Tabelle muß genutzt werden um eine Device:Reading-DropDownliste zur Erstellung eines
  5331. SVG-Plots zu erhalten. <br>
  5332. </ul>
  5333. </ul>
  5334. <br>
  5335. <ul><b>DbLogSelectionMode</b>
  5336. <ul>
  5337. <code>
  5338. attr &lt;device&gt; DbLogSelectionMode [Exclude|Include|Exclude/Include]
  5339. </code><br>
  5340. Dieses, fuer DbLog-Devices spezifische Attribut beeinflußt, wie die Device-spezifischen Attributes
  5341. DbLogExclude und DbLogInclude (s.u.) ausgewertet werden.<br>
  5342. Fehlt dieses Attribut, wird dafuer "Exclude" als Default angenommen. <br>
  5343. <ul>
  5344. <li>Exclude: DbLog verhaelt sich wie bisher auch, alles was ueber die RegExp im DEF angegeben ist, wird geloggt, bis auf das,
  5345. was ueber die RegExp in DbLogExclude ausgeschlossen wird. <br>
  5346. Das Attribut DbLogInclude wird in diesem Fall nicht beruecksichtigt</li>
  5347. <li>Include: Es wird nur das geloggt was ueber die RegExp in DbLogInclude eingeschlossen wird. <br>
  5348. Das Attribut DbLogExclude wird in diesem Fall ebenso wenig beruecksichtigt wie die Regex im DEF. </li>
  5349. <li>Exclude/Include: Funktioniert im Wesentlichen wie "Exclude", nur das sowohl DbLogExclude als auch DbLogInclude
  5350. geprueft werden. Readings die durch DbLogExclude zwar ausgeschlossen wurden, mit DbLogInclude aber wiederum eingeschlossen werden,
  5351. werden somit dennoch geloggt. </li>
  5352. </ul>
  5353. </ul>
  5354. </ul>
  5355. <br>
  5356. <ul><b>DbLogInclude</b>
  5357. <ul>
  5358. <code>
  5359. attr &lt;device&gt; DbLogInclude regex:MinInterval,[regex:MinInterval] ...
  5360. </code><br>
  5361. Wenn DbLog genutzt wird, wird in allen Devices das Attribut <i>DbLogInclude</i> propagiert.
  5362. DbLogInclude funktioniert im Endeffekt genau wie DbLogExclude, ausser dass eben readings mit diesen RegExp
  5363. in das Logging eingeschlossen werden koennen, statt ausgeschlossen.
  5364. Siehe dazu auch das DbLog-Device-Spezifische Attribut DbLogSelectionMode, das beeinflußt wie
  5365. DbLogExclude und DbLogInclude ausgewertet werden. <br>
  5366. <b>Beispiel</b> <br>
  5367. <code>attr MyDevice1 DbLogInclude .*</code> <br>
  5368. <code>attr MyDevice2 DbLogInclude state,(floorplantext|MyUserReading):300,battery:3600</code>
  5369. </ul>
  5370. </ul>
  5371. <br>
  5372. <ul><b>DbLogExclude</b>
  5373. <ul>
  5374. <code>
  5375. attr &lt;device&gt; DbLogExclude regex:MinInterval,[regex:MinInterval] ...
  5376. </code><br>
  5377. Wenn DbLog genutzt wird, wird in alle Devices das Attribut <i>DbLogExclude</i> propagiert.
  5378. Der Wert des Attributes wird als Regexp ausgewertet und schliesst die damit matchenden Readings von einem Logging aus.
  5379. Einzelne Regexp werden durch Kommata getrennt. Ist MinIntervall angegeben, so wird der Logeintrag nur
  5380. dann nicht geloggt, wenn das Intervall noch nicht erreicht und der Wert des Readings sich nicht verändert hat. <br>
  5381. <b>Beispiel</b> <br>
  5382. <code>attr MyDevice1 DbLogExclude .*</code> <br>
  5383. <code>attr MyDevice2 DbLogExclude state,(floorplantext|MyUserReading):300,battery:3600</code>
  5384. </ul>
  5385. </ul>
  5386. <br>
  5387. <ul><b>excludeDevs</b>
  5388. <ul>
  5389. <code>
  5390. attr &lt;device&gt; excludeDevs &lt;devspec1&gt;,&lt;devspec2&gt;,&lt;devspec..&gt;
  5391. </code><br>
  5392. Die Devices "devspec1", "devspec2" bis "devspec.." werden vom Logging in der Datenbank ausgeschlossen.
  5393. Diese Attribut wirkt nur wenn kein Internal "NOTIFYDEV" vorhanden ist bzw. im Define des DbLog-Devices ".*:.*"
  5394. (d.h. alle Devices werden geloggt) angegeben wurde. Dadurch können Devices explizit vom Logging ausgeschlossen werden.
  5395. Die auszuschließenden Devices können als <a href="#devspec">Geräte-Spezifikation</a> angegeben werden.
  5396. Für weitere Details bezüglich devspec siehe <a href="#devspec">Geräte-Spezifikation</a>. <br>
  5397. <b>Beispiel</b> <br>
  5398. <code>
  5399. attr &lt;device&gt; excludeDevs global,Log.*,Cam.*,TYPE=DbLog
  5400. </code><br>
  5401. # Es werden die Devices global bzw. Devices beginnend mit "Log" oder "Cam" bzw. Devices vom Typ "DbLog" vom Datenbanklogging ausgeschlossen. <br>
  5402. </ul>
  5403. </ul>
  5404. <br>
  5405. <ul><b>expimpdir</b>
  5406. <ul>
  5407. <code>
  5408. attr &lt;device&gt; expimpdir &lt;directory&gt;
  5409. </code><br>
  5410. In diesem Verzeichnis wird das Cachefile beim Export angelegt bzw. beim Import gesucht. Siehe set-Kommandos
  5411. <a href="#DbLogsetexportCache">"exportCache"</a> bzw. "importCachefile". Das Default-Verzeichnis ist "(global->modpath)/log/".
  5412. Das im Attribut angegebene Verzeichnis muss vorhanden und beschreibbar sein. <br>
  5413. <b>Beispiel</b> <br>
  5414. <code>
  5415. attr &lt;device&gt; expimpdir /opt/fhem/cache/
  5416. </code><br>
  5417. </ul>
  5418. </ul>
  5419. <br>
  5420. <ul><b>shutdownWait</b>
  5421. <ul>
  5422. <code>
  5423. attr &lt;device&gt; shutdownWait <n>
  5424. </code><br>
  5425. FHEM wartet während des shutdowns fuer n Sekunden, um die Datenbank korrekt zu beenden<br/>
  5426. </ul>
  5427. </ul>
  5428. <br>
  5429. <ul><b>noNotifyDev</b>
  5430. <ul>
  5431. <code>
  5432. attr &lt;device&gt; noNotifyDev [1|0]
  5433. </code><br>
  5434. Erzwingt dass NOTIFYDEV nicht gesetzt und somit nicht verwendet wird.<br>
  5435. </ul>
  5436. </ul>
  5437. <br>
  5438. <ul><b>noSupportPK</b>
  5439. <ul>
  5440. <code>
  5441. attr &lt;device&gt; noSupportPK [1|0]
  5442. </code><br>
  5443. Deaktiviert die programmtechnische Unterstützung eines gesetzten Primary Key durch das Modul.<br>
  5444. </ul>
  5445. </ul>
  5446. <br>
  5447. <ul><b>showproctime</b>
  5448. <ul>
  5449. <code>attr &lt;device&gt; showproctime [1|0]
  5450. </code><br>
  5451. Wenn gesetzt, zeigt das Reading "sql_processing_time" die benötigte Abarbeitungszeit (in Sekunden) für die SQL-Ausführung der
  5452. durchgeführten Funktion. Dabei wird nicht ein einzelnes SQL-Statement, sondern die Summe aller notwendigen SQL-Abfragen innerhalb der
  5453. jeweiligen Funktion betrachtet. Das Reading "background_processing_time" zeigt die im Kindprozess BlockingCall verbrauchte Zeit.<br>
  5454. </ul>
  5455. </ul>
  5456. <br>
  5457. <ul><b>showNotifyTime</b>
  5458. <ul>
  5459. <code>attr &lt;device&gt; showNotifyTime [1|0]
  5460. </code><br>
  5461. Wenn gesetzt, zeigt das Reading "notify_processing_time" die benötigte Abarbeitungszeit (in Sekunden) für die
  5462. Abarbeitung der DbLog Notify-Funktion. Das Attribut ist für Performance Analysen geeignet und hilft auch die Unterschiede
  5463. im Zeitbedarf bei der Umschaltung des synchronen in den asynchronen Modus festzustellen. <br>
  5464. </ul>
  5465. </ul>
  5466. <br>
  5467. <ul><b>syncEvents</b>
  5468. <ul>
  5469. <code>attr &lt;device&gt; syncEvents [1|0]
  5470. </code><br>
  5471. es werden Events für Reading NextSync erzeugt. <br>
  5472. </ul>
  5473. </ul>
  5474. <br>
  5475. <ul><b>syncInterval</b>
  5476. <ul>
  5477. <code>attr &lt;device&gt; syncInterval &lt;n&gt;
  5478. </code><br>
  5479. Wenn DbLog im asynchronen Modus betrieben wird (Attribut asyncMode=1), wird mit diesem Attribut das Intervall in Sekunden zur Speicherung
  5480. der im Speicher gecachten Events in die Datenbank eingestellt. Der Defaultwert ist 30 Sekunden. <br>
  5481. </ul>
  5482. </ul>
  5483. <br>
  5484. <ul><b>suppressAddLogV3</b>
  5485. <ul>
  5486. <code>attr &lt;device&gt; suppressAddLogV3 [1|0]
  5487. </code><br>
  5488. Wenn gesetzt werden verbose3-Logeinträge durch die addLog-Funktion unterdrückt. <br>
  5489. </ul>
  5490. </ul>
  5491. <br>
  5492. <ul><b>suppressUndef</b>
  5493. <ul>
  5494. <code>attr &lt;device&gt; ignoreUndef <n>
  5495. </code><br>
  5496. Unterdrueckt alle undef Werte die durch eine Get-Anfrage zb. Plot aus der Datenbank selektiert werden <br>
  5497. <b>Beispiel</b> <br>
  5498. <code>#DbLog eMeter:power:::$val=($val>1500)?undef:$val</code>
  5499. </ul>
  5500. </ul>
  5501. <br>
  5502. <ul><b>timeout</b>
  5503. <ul>
  5504. <code>
  5505. attr &lt;device&gt; timeout <n>
  5506. </code><br>
  5507. Setzt den Timeout-Wert für den Schreibzyklus in die Datenbank im asynchronen Modus (default 86400s). <br>
  5508. </ul>
  5509. </ul>
  5510. <br>
  5511. <ul><b>valueFn</b>
  5512. <ul>
  5513. <code>
  5514. attr &lt;device&gt; valueFn {}
  5515. </code><br>
  5516. Es kann über einen Perl-Ausdruck auf die Variablen $TIMESTAMP, $DEVICE, $DEVICETYPE, $READING, $VALUE (Wert des Readings) und
  5517. $UNIT (Einheit des Readingswert) zugegriffen werden und diese verändern, d.h. die veränderten Werte werden geloggt.
  5518. Außerdem hat man lesenden Zugriff auf $EVENT für eine Auswertung im Perl-Ausdruck.
  5519. Diese Variable kann aber nicht verändert werden. <br>
  5520. Soll $TIMESTAMP verändert werden, muss die Form "yyyy-mm-dd hh:mm:ss" eingehalten werden, ansonsten wird der
  5521. geänderte $timestamp nicht übernommen.
  5522. Zusätzlich kann durch Setzen der Variable "$IGNORE=1" ein Datensatz vom Logging ausgeschlossen werden. <br><br>
  5523. <b>Beispiele</b> <br>
  5524. <code>
  5525. attr &lt;device&gt; valueFn {if ($DEVICE eq "living_Clima" && $VALUE eq "off" ){$VALUE=0;} elsif ($DEVICE eq "e-power"){$VALUE= sprintf "%.1f", $VALUE;}}
  5526. </code> <br>
  5527. # ändert den Reading-Wert des Gerätes "living_Clima" von "off" zu "0" und rundet den Wert vom Gerät "e-power" <br><br>
  5528. <code>
  5529. attr &lt;device&gt; valueFn {if ($DEVICE eq "SMA_Energymeter" && $READING eq "state"){$IGNORE=1;}}
  5530. </code><br>
  5531. # der Datensatz wird nicht geloggt wenn Device = "SMA_Energymeter" und das Reading = "state" ist <br><br>
  5532. <code>
  5533. attr &lt;device&gt; valueFn {if ($DEVICE eq "Dum.Energy" && $READING eq "TotalConsumption"){$UNIT="W";}}
  5534. </code><br>
  5535. # setzt die Einheit des Devices "Dum.Energy" auf "W" wenn das Reading = "TotalConsumption" ist <br><br>
  5536. </ul>
  5537. </ul>
  5538. <br>
  5539. <ul><b>verbose4Devs</b>
  5540. <ul>
  5541. <code>
  5542. attr &lt;device&gt; verbose4Devs &lt;device1&gt;,&lt;device2&gt;,&lt;device..&gt;
  5543. </code><br>
  5544. Mit verbose Level 4 werden nur Ausgaben bezüglich der in diesem Attribut aufgeführten Devices im Logfile protokolliert. Ohne dieses
  5545. Attribut werden mit verbose 4 Ausgaben aller relevanten Devices im Logfile protokolliert.
  5546. Die angegebenen Devices werden als Regex ausgewertet. <br>
  5547. <b>Beispiel</b> <br>
  5548. <code>
  5549. attr &lt;device&gt; verbose4Devs sys.*,.*5000.*,Cam.*,global
  5550. </code><br>
  5551. # Es werden Devices beginnend mit "sys", "Cam" bzw. Devices die "5000" enthalten und das Device "global" protokolliert falls verbose=4
  5552. eingestellt ist. <br>
  5553. </ul>
  5554. </ul>
  5555. <br>
  5556. </ul>
  5557. =end html_DE
  5558. =cut