38_netatmo.pm 212 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054
  1. ##############################################################################
  2. # $Id: 38_netatmo.pm 14411 2017-05-29 09:33:00Z moises $
  3. #
  4. # 38_netatmo.pm
  5. #
  6. # 2017 Markus M.
  7. # Based on original code by justme1968
  8. #
  9. # https://forum.fhem.de/index.php/topic,53500.0.html
  10. #
  11. #
  12. ##############################################################################
  13. # Release 12 / 2017-05-29
  14. package main;
  15. use strict;
  16. use warnings;
  17. use Encode qw(encode_utf8 decode_utf8);
  18. use JSON;
  19. use Math::Trig;
  20. use HttpUtils;
  21. use Data::Dumper; #debugging
  22. use MIME::Base64;
  23. use vars qw($FW_ME);
  24. use vars qw($FW_CSRF);
  25. my %health_index = ( 0 => "healthy",
  26. 1 => "fine",
  27. 2 => "fair",
  28. 3 => "poor",
  29. 4 => "unhealthy",
  30. 5 => "unknown", );
  31. sub
  32. netatmo_Initialize($)
  33. {
  34. my ($hash) = @_;
  35. $hash->{DefFn} = "netatmo_Define";
  36. $hash->{NotifyFn} = "netatmo_Notify";
  37. $hash->{UndefFn} = "netatmo_Undefine";
  38. $hash->{SetFn} = "netatmo_Set";
  39. $hash->{GetFn} = "netatmo_Get";
  40. $hash->{DbLog_splitFn} = "netatmo_DbLog_splitFn";
  41. $hash->{AttrFn} = "netatmo_Attr";
  42. $hash->{AttrList} = "IODev ".
  43. "disable:1 ".
  44. "interval ".
  45. "videoquality:poor,low,medium,high ".
  46. "ignored_device_ids ".
  47. "setpoint_duration ".
  48. "webhookURL webhookPoll:0,1 ".
  49. "addresslimit ";
  50. $hash->{AttrList} .= $readingFnAttributes;
  51. }
  52. #####################################
  53. sub
  54. netatmo_Define($$)
  55. {
  56. my ($hash, $def) = @_;
  57. my @a = split("[ \t][ \t]*", $def);
  58. my $name = $a[0];
  59. $hash->{status} = "initialized";
  60. my $subtype;
  61. if($a[2] eq "WEBHOOK") {
  62. $subtype = "WEBHOOK";
  63. my $d = $modules{$hash->{TYPE}}{defptr}{"WEBHOOK"};
  64. return "Netatmo webkook already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  65. $modules{$hash->{TYPE}}{defptr}{"WEBHOOK"} = $hash;
  66. readingsBeginUpdate($hash);
  67. readingsBulkUpdate( $hash, "webhook", "initialized" );
  68. readingsEndUpdate( $hash, 1 );
  69. my $account = $modules{$hash->{TYPE}}{defptr}{"account"};
  70. $hash->{IODev} = $account;
  71. $attr{$name}{IODev} = $account->{NAME} if( !defined($attr{$name}{IODev}) && $account);
  72. }
  73. elsif( @a == 3 ) {
  74. $subtype = "DEVICE";
  75. my $device = $a[2];
  76. $hash->{Device} = $device;
  77. $hash->{openRequests} = 0;
  78. $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  79. my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"};
  80. return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  81. $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash;
  82. }
  83. elsif( ($a[2] eq "PUBLIC" && @a > 3 ) )
  84. {
  85. $hash->{openRequests} = 0;
  86. Log3 $name, 5, "$name: pub ".Dumper(@a);
  87. if( $a[3] && $a[3] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ )
  88. {
  89. my $device = $a[3];
  90. $hash->{Device} = $device;
  91. if( $a[4] && $a[4] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ )
  92. {
  93. $subtype = "MODULE";
  94. my $module = "";
  95. my $readings = "";
  96. my @a = splice( @a, 4 );
  97. while( @a ) {
  98. $module .= " " if( $module );
  99. $module .= shift( @a );
  100. $readings .= " " if( $readings );
  101. $readings .= shift( @a );
  102. }
  103. $hash->{Module} = $module;
  104. $hash->{dataTypes} = $readings if($readings);
  105. $hash->{dataTypes} = "Temperature,CO2,Humidity,Noise,Pressure,Rain,WindStrength,WindAngle,GustStrength,GustAngle,Sp_Temperature,BoilerOn,BoilerOff" if( !$readings );
  106. my $d = $modules{$hash->{TYPE}}{defptr}{"M$module"};
  107. return "module $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  108. $modules{$hash->{TYPE}}{defptr}{"M$module"} = $hash;
  109. my $state_format;
  110. if( $readings =~ m/temperature/ ) {
  111. $state_format .= " " if( $state_format );
  112. $state_format .= "T: temperature";
  113. }
  114. if( $readings =~ m/humidity/ ) {
  115. $state_format .= " " if( $state_format );
  116. $state_format .= "H: humidity";
  117. }
  118. $attr{$name}{stateFormat} = $state_format if( !defined($attr{$name}{stateFormat}) && defined($state_format) && defined($name) );
  119. $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name));
  120. $attr{$name}{devStateIcon} = ".*:no-icon" if( !defined($attr{$name}{devStateIcon}) && defined($name));
  121. }
  122. $subtype = "DEVICE";
  123. my $d = $modules{$hash->{TYPE}}{defptr}{"D$device"};
  124. return "device $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  125. $modules{$hash->{TYPE}}{defptr}{"D$device"} = $hash;
  126. delete( $hash->{LAST_POLL} );
  127. }
  128. else
  129. {
  130. my ($lat, $lon, $rad);
  131. if($a[3] =~ m/,/){
  132. Log3 $name, 5, "$name: latlng 2 ".$a[3];
  133. my @latlon = split( ',', $a[3] );
  134. $lat = $latlon[0];
  135. $lon = $latlon[1];
  136. $rad = $a[4];
  137. }
  138. else {
  139. $lat = $a[3];
  140. $lon = $a[4];
  141. $rad = $a[5];
  142. }
  143. $rad = 0.02 if( !$rad );
  144. $hash->{Lat} = $lat;
  145. $hash->{Lon} = $lon;
  146. $hash->{Rad} = $rad;
  147. $subtype = "PUBLIC";
  148. $modules{$hash->{TYPE}}{defptr}{$hash->{Lat}.$hash->{Lon}.$hash->{Rad}} = $hash;
  149. my $account = $modules{$hash->{TYPE}}{defptr}{"account"};
  150. $hash->{IODev} = $account;
  151. $attr{$name}{IODev} = $account->{NAME} if( !defined($attr{$name}{IODev}) && $account);
  152. }
  153. $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} );
  154. $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name));
  155. $attr{$name}{devStateIcon} = ".*:no-icon" if( !defined($attr{$name}{devStateIcon}) && defined($name));
  156. } elsif( ($a[2] eq "MODULE" && @a == 5 ) ) {
  157. $subtype = "MODULE";
  158. my $device = $a[@a-2];
  159. my $module = $a[@a-1];
  160. $hash->{Device} = $device;
  161. $hash->{Module} = $module;
  162. $hash->{openRequests} = 0;
  163. $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  164. my $d = $modules{$hash->{TYPE}}{defptr}{"M$module"};
  165. return "module $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  166. $modules{$hash->{TYPE}}{defptr}{"M$module"} = $hash;
  167. } elsif( ($a[2] eq "FORECAST" && @a == 4 ) ) {
  168. $subtype = "FORECAST";
  169. my $device = $a[3];
  170. $hash->{Station} = $device;
  171. $hash->{openRequests} = 0;
  172. $hash->{INTERVAL} = 60*60 if( !$hash->{INTERVAL} );
  173. $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name));
  174. $attr{$name}{devStateIcon} = ".*:no-icon" if( !defined($attr{$name}{devStateIcon}) && defined($name));
  175. $attr{$name}{'event-on-change-reading'} = ".*" if( !defined($attr{$name}{'event-on-change-reading'}) && defined($name));
  176. my $d = $modules{$hash->{TYPE}}{defptr}{"F$device"};
  177. return "forecast $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  178. $modules{$hash->{TYPE}}{defptr}{"F$device"} = $hash;
  179. my $account = $modules{$hash->{TYPE}}{defptr}{"account"};
  180. $hash->{IODev} = $account;
  181. $attr{$name}{IODev} = $account->{NAME} if( !defined($attr{$name}{IODev}) && $account);
  182. } elsif( ($a[2] eq "RELAY" && @a == 4 ) ) {
  183. $subtype = "RELAY";
  184. my $device = $a[3];
  185. $hash->{Relay} = $device;
  186. $hash->{openRequests} = 0;
  187. $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} );
  188. my $d = $modules{$hash->{TYPE}}{defptr}{"R$device"};
  189. return "relay $device already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  190. $modules{$hash->{TYPE}}{defptr}{"R$device"} = $hash;
  191. } elsif( ($a[2] eq "THERMOSTAT" && @a == 5 ) ) {
  192. $subtype = "THERMOSTAT";
  193. my $device = $a[@a-2];
  194. my $module = $a[@a-1];
  195. $hash->{Relay} = $device;
  196. $hash->{Thermostat} = $module;
  197. $hash->{openRequests} = 0;
  198. $hash->{dataTypes} = "Temperature,Sp_Temperature,BoilerOn,BoilerOff";
  199. $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} );
  200. my $d = $modules{$hash->{TYPE}}{defptr}{"T$module"};
  201. return "thermostat $module already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  202. $modules{$hash->{TYPE}}{defptr}{"T$module"} = $hash;
  203. } elsif( ($a[2] eq "HOME" && @a == 4 ) ) {
  204. $subtype = "HOME";
  205. my $home = $a[@a-1];
  206. $hash->{Home} = $home;
  207. $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  208. $attr{$name}{videoquality} = "medium" if( !defined($attr{$name}{videoquality}) && defined($name));
  209. my $d = $modules{$hash->{TYPE}}{defptr}{"H$home"};
  210. return "home $home already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  211. $modules{$hash->{TYPE}}{defptr}{"H$home"} = $hash;
  212. } elsif( ($a[2] eq "PERSON" && @a == 5 ) ) {
  213. $subtype = "PERSON";
  214. my $home = $a[@a-2];
  215. my $person = $a[@a-1];
  216. $hash->{Home} = $home;
  217. $hash->{Person} = $person;
  218. $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  219. my $d = $modules{$hash->{TYPE}}{defptr}{"P$person"};
  220. return "person $person already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  221. $modules{$hash->{TYPE}}{defptr}{"P$person"} = $hash;
  222. } elsif( ($a[2] eq "CAMERA" && @a == 5 ) ) {
  223. $subtype = "CAMERA";
  224. my $home = $a[@a-2];
  225. my $camera = $a[@a-1];
  226. $hash->{Home} = $home;
  227. $hash->{Camera} = $camera;
  228. $hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  229. my $d = $modules{$hash->{TYPE}}{defptr}{"C$camera"};
  230. return "camera $camera already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  231. $modules{$hash->{TYPE}}{defptr}{"C$camera"} = $hash;
  232. } elsif( ($a[2] eq "TAG" && @a == 5 ) ) {
  233. $subtype = "TAG";
  234. my $camera = $a[@a-2];
  235. my $tag = $a[@a-1];
  236. $hash->{Tag} = $tag;
  237. $hash->{Camera} = $camera;
  238. #$hash->{INTERVAL} = 60*15 if( !$hash->{INTERVAL} );
  239. my $d = $modules{$hash->{TYPE}}{defptr}{"G$tag"};
  240. return "tag $tag already defined as $d->{NAME}" if( defined($d) && $d->{NAME} ne $name );
  241. $modules{$hash->{TYPE}}{defptr}{"G$tag"} = $hash;
  242. } elsif( @a == 6 || ($a[2] eq "ACCOUNT" && @a == 7 ) ) {
  243. $subtype = "ACCOUNT";
  244. $hash->{network} = "ok";
  245. delete($hash->{access_token});
  246. delete($hash->{access_token_app});
  247. delete($hash->{refresh_token});
  248. delete($hash->{refresh_token_app});
  249. delete($hash->{expires_at});
  250. delete($hash->{expires_at_app});
  251. delete($hash->{csrf_token});
  252. my $user = $a[@a-4];
  253. my $pass = $a[@a-3];
  254. my $username = netatmo_encrypt($user);
  255. my $password = netatmo_encrypt($pass);
  256. Log3 $name, 2, "$name: encrypt $user/$pass to $username/$password" if($user ne $username || $pass ne $password);
  257. my $client_id = $a[@a-2];
  258. my $client_secret = $a[@a-1];
  259. #$hash->{DEF} =~ s/$user/$username/g;
  260. #$hash->{DEF} =~ s/$pass/$password/g;
  261. $hash->{DEF} = "ACCOUNT $username $password $client_id $client_secret";
  262. $hash->{Clients} = ":netatmo:";
  263. $hash->{helper}{username} = $username;
  264. $hash->{helper}{password} = $password;
  265. $hash->{helper}{client_id} = $client_id;
  266. $hash->{helper}{client_secret} = $client_secret;
  267. $hash->{INTERVAL} = 60*60 if( !$hash->{INTERVAL} );
  268. $attr{$name}{room} = "netatmo" if( !defined($attr{$name}{room}) && defined($name));
  269. $modules{$hash->{TYPE}}{defptr}{"account"} = $hash;
  270. } else {
  271. return "Usage: define <name> netatmo device\
  272. define <name> netatmo userid publickey\
  273. define <name> netatmo PUBLIC latitude longitude [radius]\
  274. define <name> netatmo [ACCOUNT] username password" if(@a < 3 || @a > 5);
  275. }
  276. $hash->{NAME} = $name;
  277. $hash->{SUBTYPE} = $subtype;
  278. $hash->{STATE} = "Initialized";
  279. $hash->{NOTIFYDEV} = "global";
  280. if( $init_done ) {
  281. netatmo_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  282. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  283. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "MODULE" );
  284. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "PUBLIC" );
  285. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "FORECAST" );
  286. netatmo_initHome($hash) if( $hash->{SUBTYPE} eq "HOME" );
  287. netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" );
  288. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" );
  289. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" );
  290. netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" );
  291. }
  292. else
  293. {
  294. InternalTimer(gettimeofday()+120, "netatmo_InitWait", $hash);
  295. }
  296. return undef;
  297. }
  298. sub netatmo_InitWait($) {
  299. my ($hash) = @_;
  300. my $name = $hash->{NAME};
  301. Log3 "netatmo", 5, "netatmo: initwait ".$init_done;
  302. RemoveInternalTimer($hash);
  303. if( $init_done ) {
  304. netatmo_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  305. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  306. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "MODULE" );
  307. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "PUBLIC" );
  308. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "FORECAST" );
  309. netatmo_initHome($hash) if( $hash->{SUBTYPE} eq "HOME" );
  310. netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" );
  311. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" );
  312. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" );
  313. netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" );
  314. }
  315. else
  316. {
  317. InternalTimer(gettimeofday()+120, "netatmo_InitWait", $hash);
  318. }
  319. return undef;
  320. }
  321. sub
  322. netatmo_Notify($$)
  323. {
  324. my ($hash,$dev) = @_;
  325. my $name = $hash->{NAME};
  326. return if($dev->{NAME} ne "global");
  327. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  328. RemoveInternalTimer($hash);
  329. netatmo_connect($hash) if( $hash->{SUBTYPE} eq "ACCOUNT" );
  330. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "DEVICE" );
  331. netatmo_initDevice($hash) if( $hash->{SUBTYPE} eq "MODULE" );
  332. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "PUBLIC" );
  333. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "FORECAST" );
  334. netatmo_initHome($hash) if( $hash->{SUBTYPE} eq "HOME" );
  335. netatmo_pingCamera($hash) if( $hash->{SUBTYPE} eq "CAMERA" );
  336. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "RELAY" );
  337. netatmo_poll($hash) if( $hash->{SUBTYPE} eq "THERMOSTAT" );
  338. netatmo_addExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" );
  339. return undef;
  340. }
  341. sub
  342. netatmo_Undefine($$)
  343. {
  344. my ($hash, $arg) = @_;
  345. RemoveInternalTimer($hash);
  346. delete( $modules{$hash->{TYPE}}{defptr}{"D$hash->{Device}"} ) if( $hash->{SUBTYPE} eq "DEVICE" );
  347. delete( $modules{$hash->{TYPE}}{defptr}{"M$hash->{Module}"} ) if( $hash->{SUBTYPE} eq "MODULE" );
  348. delete( $modules{$hash->{TYPE}}{defptr}{$hash->{Lat}.$hash->{Lon}.$hash->{Rad}} ) if( $hash->{SUBTYPE} eq "PUBLIC" );
  349. delete( $modules{$hash->{TYPE}}{defptr}{"F$hash->{Station}"} ) if( $hash->{SUBTYPE} eq "FORECAST" );
  350. delete( $modules{$hash->{TYPE}}{defptr}{"H$hash->{Home}"} ) if( $hash->{SUBTYPE} eq "HOME" );
  351. delete( $modules{$hash->{TYPE}}{defptr}{"C$hash->{Camera}"} ) if( $hash->{SUBTYPE} eq "CAMERA" );
  352. delete( $modules{$hash->{TYPE}}{defptr}{"P$hash->{Person}"} ) if( $hash->{SUBTYPE} eq "PERSON" );
  353. delete( $modules{$hash->{TYPE}}{defptr}{"R$hash->{Relay}"} ) if( $hash->{SUBTYPE} eq "RELAY" );
  354. delete( $modules{$hash->{TYPE}}{defptr}{"T$hash->{Thermostat}"} ) if( $hash->{SUBTYPE} eq "THERMOSTAT" );
  355. netatmo_removeExtension($hash) if( $hash->{SUBTYPE} eq "WEBHOOK" );
  356. return undef;
  357. }
  358. sub
  359. netatmo_Set($$@)
  360. {
  361. my ($hash, $name, $cmd, @parameters) = @_;
  362. my $list = "";
  363. $list = "autocreate:noArg autocreate_homes:noArg autocreate_thermostats:noArg autocreate_homecoachs:noArg" if( $hash->{SUBTYPE} eq "ACCOUNT" );
  364. #$list .= " unban:noArg" if( $hash->{SUBTYPE} eq "ACCOUNT" );
  365. $list = "home:noArg away:noArg" if ($hash->{SUBTYPE} eq "PERSON");
  366. $list = "empty:noArg notify_movements:never,empty,always notify_unknowns:empty,always record_movements:never,empty,always record_alarms:never,empty,always presence_record_humans:ignore,record,record_and_notify presence_record_vehicles:ignore,record,record_and_notify presence_record_animals:ignore,record,record_and_notify presence_record_movements:ignore,record,record_and_notify presence_record_alarms:ignore,record,record_and_notify gone_after presence_enable_notify_from_to:empty,always presence_notify_from presence_notify_to smart_notifs:on,off" if ($hash->{SUBTYPE} eq "HOME");
  367. $list = "enable disable irmode:auto,always,never led_on_live:on,off mirror:off,on audio:on,off" if ($hash->{SUBTYPE} eq "CAMERA");
  368. $list = "enable disable light_mode:auto,on,off floodlight intensity:slider,0,1,100 night_always:true,false night_person:true,false night_vehicle:true,false night_animal:true,false night_movement:true,false" if ($hash->{SUBTYPE} eq "CAMERA" && defined($hash->{model}) && $hash->{model} eq "NOC");
  369. $list = "calibrate:noArg" if ($hash->{SUBTYPE} eq "TAG");
  370. if ($hash->{SUBTYPE} eq "THERMOSTAT")
  371. {
  372. $list = "setpoint_mode:off,hg,away,program,manual,max setpoint_temp:5.0,5.5,6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,10.5,11.0,11.5,12.0,12.5,13.0,13.5,14.0,14.5,15.0,15.5,16.0,16.5,17.0,17.5,18.0,18.5,19.0,19.5,20.0,20.5,21.0,21.5,22.0,22.5,23.0,23.5,24.0,24.5,25.0,25.5,26.0,26.5,27.0,27.5,28.0,28.5,29.0,29.5,30.0";
  373. $list = "setpoint_mode:off,hg,away,program,manual,max program:".$hash->{schedulenames}." setpoint_temp:5.0,5.5,6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,10.5,11.0,11.5,12.0,12.5,13.0,13.5,14.0,14.5,15.0,15.5,16.0,16.5,17.0,17.5,18.0,18.5,19.0,19.5,20.0,20.5,21.0,21.5,22.0,22.5,23.0,23.5,24.0,24.5,25.0,25.5,26.0,26.5,27.0,27.5,28.0,28.5,29.0,29.5,30.0" if(defined($hash->{schedulenames}));
  374. }
  375. $list = "clear:noArg webhook:add,drop" if ($hash->{SUBTYPE} eq "WEBHOOK");
  376. return undef if( $list eq "" );
  377. if( $cmd eq "autocreate" ) {
  378. return netatmo_autocreate($hash, 1 );
  379. return undef;
  380. }
  381. elsif( $cmd eq "autocreate_homes" ) {
  382. return netatmo_autocreatehome($hash, 1 );
  383. return undef;
  384. }
  385. elsif( $cmd eq "autocreate_thermostats" ) {
  386. return netatmo_autocreatethermostat($hash, 1 );
  387. return undef;
  388. }
  389. elsif( $cmd eq "autocreate_homecoachs" ) {
  390. return netatmo_autocreatehomecoach($hash, 1 );
  391. return undef;
  392. }
  393. elsif( $cmd eq "home" ) {
  394. return netatmo_setPresence($hash, "home");
  395. return undef;
  396. }
  397. elsif( $cmd eq "away" ) {
  398. return netatmo_setPresence($hash, "away");
  399. return undef;
  400. }
  401. elsif( $cmd eq "empty" ) {
  402. return netatmo_setPresence($hash, "empty");
  403. return undef;
  404. }
  405. elsif( $cmd =~ /^notify_/ || $cmd =~ /^record_/ || $cmd =~ /^presence_/ || $cmd eq "gone_after" || $cmd eq "smart_notifs" ) {
  406. return netatmo_setNotifications($hash, $cmd, $parameters[0]);
  407. return undef;
  408. }
  409. elsif( $cmd eq "enable" ) {
  410. my $pin = $parameters[0];
  411. $pin = "0000" if(!defined($pin) || length($pin) != 4);
  412. return netatmo_setCamera($hash, "on", $pin);
  413. return undef;
  414. }
  415. elsif( $cmd eq "disable" ) {
  416. my $pin = $parameters[0];
  417. $pin = "0000" if(!defined($pin) || length($pin) != 4);
  418. $hash->{pin} = $pin;
  419. return netatmo_setCamera($hash, "off", $pin);
  420. return undef;
  421. }
  422. elsif( $cmd eq "irmode" || $cmd eq "led_on_live" || $cmd eq "mirror" || $cmd eq "audio" ) {
  423. my $setting = $parameters[0];
  424. return "You have to define a value" if(!defined($setting) || $setting eq "");
  425. readingsSingleUpdate($hash, $cmd, $setting, 1);
  426. return netatmo_setCameraSetting($hash, $cmd, $setting);
  427. return undef;
  428. }
  429. elsif( $cmd eq "light_mode" ) {
  430. my $setting = $parameters[0];
  431. return "You have to define a value" if(!defined($setting) || $setting eq "");
  432. return netatmo_setFloodlight($hash, $setting);
  433. return undef;
  434. }
  435. elsif( $cmd eq "floodlight" ) {
  436. my $setting = $parameters[0];
  437. $setting = 100 if(!defined($setting) || $setting eq "");
  438. $setting = int($setting);
  439. return netatmo_setIntensity($hash, $setting);
  440. return undef;
  441. }
  442. elsif( $cmd eq "intensity" || $cmd eq "night_always" || $cmd eq "night_person" || $cmd eq "night_vehicle" || $cmd eq "night_animal" || $cmd eq "night_movement" ) {
  443. my $setting = $parameters[0];
  444. return "You have to define a value" if(!defined($setting) || $setting eq "");
  445. readingsSingleUpdate($hash, $cmd, $setting, 1);
  446. return netatmo_setPresenceConfig($hash, $setting);
  447. return undef;
  448. }
  449. elsif( $cmd eq "calibrate" ) {
  450. return netatmo_setTagCalibration($hash, $cmd);
  451. return undef;
  452. }
  453. elsif( $cmd eq "setpoint_mode" ) {
  454. my $setting = $parameters[0];
  455. my $duration = $parameters[1];
  456. return "You have to define a mode" if(!defined($setting) || $setting eq "");
  457. return netatmo_setThermostatMode($hash,$setting,$duration);
  458. return undef;
  459. }
  460. elsif( $cmd eq "setpoint_temp" ) {
  461. my $setting = $parameters[0];
  462. my $duration = $parameters[1];
  463. return "You have to define a temperature" if(!defined($setting) || $setting eq "");
  464. return netatmo_setThermostatTemp($hash,$setting,$duration);
  465. return undef;
  466. }
  467. elsif( $cmd eq "program" ) {
  468. my $setting = $parameters[0];
  469. return "You have to define a program" if(!defined($setting) || $setting eq "");
  470. return netatmo_setThermostatProgram($hash,$setting);
  471. return undef;
  472. }
  473. elsif( $cmd eq "clear" ) {
  474. delete $hash->{READINGS};
  475. return undef;
  476. }
  477. elsif( $cmd eq "webhook" ) {
  478. if($parameters[0] eq "drop")
  479. {
  480. netatmo_dropWebhook($hash);
  481. } else {
  482. netatmo_registerWebhook($hash);
  483. }
  484. return undef;
  485. }
  486. if( $cmd eq 'unban' )# unban:noArg
  487. {
  488. return netatmo_Unban($hash);
  489. }
  490. return "Unknown argument $cmd, choose one of $list";
  491. }
  492. sub
  493. netatmo_getToken($)
  494. {
  495. my ($hash) = @_;
  496. my $name = $hash->{NAME};
  497. return Log3 $name, 1, "$name: No client id was found! (getToken)" if(!defined($hash->{helper}{client_id}));
  498. return Log3 $name, 1, "$name: No client secret was found! (getToken)" if(!defined($hash->{helper}{client_secret}));
  499. return Log3 $name, 1, "$name: No username was found! (getToken)" if(!defined($hash->{helper}{username}));
  500. return Log3 $name, 1, "$name: No password was found! (getToken)" if(!defined($hash->{helper}{password}));
  501. my($err,$data) = HttpUtils_BlockingGet({
  502. url => "https://api.netatmo.com/oauth2/token",
  503. timeout => 5,
  504. noshutdown => 1,
  505. data => {grant_type => 'password', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, username => netatmo_decrypt($hash->{helper}{username}), password => netatmo_decrypt($hash->{helper}{password}), scope => 'read_station read_thermostat write_thermostat write_camera read_camera access_camera read_presence write_presence access_presence read_homecoach'},
  506. });
  507. netatmo_dispatch( {hash=>$hash,type=>'token'},$err,$data );
  508. }
  509. sub
  510. netatmo_getAppToken($)
  511. {
  512. my ($hash) = @_;
  513. my $name = $hash->{NAME};
  514. return Log3 $name, 1, "$name: No username was found! (getAppToken)" if(!defined($hash->{helper}{username}));
  515. return Log3 $name, 1, "$name: No password was found! (getAppToken)" if(!defined($hash->{helper}{password}));
  516. #my $auth = "QXV0aG9yaXphdGlvbjogQmFzaWMgYm1GZlkyeHBaVzUwWDJsdmMxOTNaV3hqYjIxbE9qaGhZalU0TkdRMk1tTmhNbUUzTjJVek4yTmpZelppTW1NM1pUUm1Namxs";
  517. my $auth = "QXV0aG9yaXphdGlvbjogQmFzaWMgYm1GZlkyeHBaVzUwWDJsdmN6bzFObU5qTmpSaU56azBOak5oT1RrMU9HSTNOREF4TkRjeVpEbGxNREUxT0E9PQ==";
  518. $auth = decode_base64($auth);
  519. my($err,$data) = HttpUtils_BlockingGet({
  520. url => "https://app.netatmo.net/oauth2/token",
  521. method => "POST",
  522. timeout => 5,
  523. noshutdown => 1,
  524. header => "$auth",
  525. data => {app_identifier=>'com.netatmo.camera', grant_type => 'password', password => netatmo_decrypt($hash->{helper}{password}), scope => 'write_camera read_camera access_camera read_presence write_presence access_presence read_station', username => netatmo_decrypt($hash->{helper}{username})},
  526. });
  527. netatmo_dispatch( {hash=>$hash,type=>'apptoken'},$err,$data );
  528. }
  529. sub
  530. netatmo_refreshToken($;$)
  531. {
  532. my ($hash,$nonblocking) = @_;
  533. my $name = $hash->{NAME};
  534. if( defined($hash->{access_token}) && defined($hash->{expires_at}) ) {
  535. my ($seconds) = gettimeofday();
  536. return undef if( $seconds < $hash->{expires_at} - 300 );
  537. }
  538. Log3 $name, 3, "$name: refreshing token";
  539. my $resolve = inet_aton("api.netatmo.com");
  540. if(!defined($resolve))
  541. {
  542. $hash->{STATE} = "DNS error";
  543. $hash->{network} = "dns" if($hash->{SUBTYPE} eq "ACCOUNT");
  544. delete($hash->{access_token});
  545. delete($hash->{access_token_app});
  546. InternalTimer( gettimeofday() + 1800, "netatmo_refreshTokenTimer", $hash);
  547. Log3 $name, 1, "$name: DNS error, cannot resolve api.netatmo.com";
  548. return undef;
  549. } else {
  550. $hash->{network} = "ok";
  551. }
  552. if( !$hash->{refresh_token} ) {
  553. netatmo_getToken($hash);
  554. return undef;
  555. }
  556. if( $nonblocking ) {
  557. HttpUtils_NonblockingGet({
  558. url => "https://api.netatmo.com/oauth2/token",
  559. timeout => 20,
  560. noshutdown => 1,
  561. data => {grant_type => 'refresh_token', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, refresh_token => $hash->{refresh_token}},
  562. hash => $hash,
  563. type => 'token',
  564. callback => \&netatmo_dispatch,
  565. });
  566. } else {
  567. my($err,$data) = HttpUtils_BlockingGet({
  568. url => "https://api.netatmo.com/oauth2/token",
  569. timeout => 5,
  570. noshutdown => 1,
  571. data => {grant_type => 'refresh_token', client_id => $hash->{helper}{client_id}, client_secret=> $hash->{helper}{client_secret}, refresh_token => $hash->{refresh_token}},
  572. });
  573. netatmo_dispatch( {hash=>$hash,type=>'token'},$err,$data );
  574. }
  575. }
  576. sub
  577. netatmo_refreshAppToken($;$)
  578. {
  579. my ($hash,$nonblocking) = @_;
  580. my $name = $hash->{NAME};
  581. if($hash->{network} eq "dns")
  582. {
  583. Log3 $name, 2, "$name: app token dns error, update postponed!";
  584. InternalTimer( gettimeofday() + 600, "netatmo_refreshAppTokenTimer", $hash);
  585. return undef;
  586. }
  587. if( defined($hash->{access_token_app}) && defined($hash->{expires_at_app}) ) {
  588. my ($seconds) = gettimeofday();
  589. return undef if( $seconds < $hash->{expires_at_app} - 300 );
  590. } elsif( !defined($hash->{refresh_token_app}) ) {
  591. Log3 $name, 2, "$name: missing app refresh token!";
  592. netatmo_getAppToken($hash);
  593. return undef;
  594. }
  595. delete($hash->{csrf_token});
  596. Log3 $name, 3, "$name: refreshing app token";
  597. my $auth = "QXV0aG9yaXphdGlvbjogQmFzaWMgYm1GZlkyeHBaVzUwWDJsdmN6bzFObU5qTmpSaU56azBOak5oT1RrMU9HSTNOREF4TkRjeVpEbGxNREUxT0E9PQ==";
  598. $auth = decode_base64($auth);
  599. if( $nonblocking ) {
  600. HttpUtils_NonblockingGet({
  601. url => "https://app.netatmo.net/oauth2/token",
  602. timeout => 20,
  603. noshutdown => 1,
  604. header => "$auth",
  605. data => {grant_type => 'refresh_token', refresh_token => $hash->{refresh_token_app}},
  606. hash => $hash,
  607. type => 'apptoken',
  608. callback => \&netatmo_dispatch,
  609. });
  610. } else {
  611. my($err,$data) = HttpUtils_BlockingGet({
  612. url => "https://app.netatmo.net/oauth2/token",
  613. timeout => 5,
  614. noshutdown => 1,
  615. header => "$auth",
  616. data => {grant_type => 'refresh_token', refresh_token => $hash->{refresh_token_app}},
  617. });
  618. netatmo_dispatch( {hash=>$hash,type=>'apptoken'},$err,$data );
  619. }
  620. }
  621. sub
  622. netatmo_refreshTokenTimer($)
  623. {
  624. my ($hash) = @_;
  625. my $name = $hash->{NAME};
  626. Log3 $name, 5, "$name: refreshing token (timer)";
  627. netatmo_refreshToken($hash, 1);
  628. }
  629. sub
  630. netatmo_refreshAppTokenTimer($)
  631. {
  632. my ($hash) = @_;
  633. my $name = $hash->{NAME};
  634. Log3 $name, 5, "$name: refreshing app token (timer)";
  635. netatmo_refreshAppToken($hash, 1);
  636. }
  637. sub
  638. netatmo_checkConnection($)
  639. {
  640. my ($hash) = @_;
  641. my $name = $hash->{NAME};
  642. return undef if($hash->{network} eq "ok");
  643. Log3 $name, 3, "$name: refreshing connection information";
  644. HttpUtils_NonblockingGet({
  645. url => "https://api.netatmo.com/api/readtimeline",
  646. timeout => 5,
  647. hash => $hash,
  648. callback => \&netatmo_parseConnection,
  649. });
  650. return undef;
  651. }
  652. sub
  653. netatmo_parseConnection($$$)
  654. {
  655. my ($param,$err,$data) = @_;
  656. my $hash = $param->{hash};
  657. my $name = $hash->{NAME};
  658. if( $err ) {
  659. Log3 $name, 1, "$name: connection check failed: $err";
  660. if($err =~ /refused/ ){
  661. RemoveInternalTimer($hash);
  662. $hash->{status} = "banned";
  663. $hash->{network} = "banned";
  664. }
  665. elsif($err =~ /Bad hostname/ || $err =~ /gethostbyname/){
  666. $hash->{status} = "timeout";
  667. $hash->{network} = "dns";
  668. }
  669. elsif($err =~ /timed out/){
  670. $hash->{status} = "timeout";
  671. $hash->{network} = "timeout";
  672. }
  673. elsif($err =~ /Can't connect/){
  674. $hash->{status} = "timeout";
  675. $hash->{network} = "disconnected";
  676. }
  677. return undef;
  678. } elsif( $data ) {
  679. $data =~ s/\n//g;
  680. if( $data !~ m/^{.*}$/ ) {
  681. Log3 $name, 2, "$name: invalid json on connection check";
  682. return undef;
  683. }
  684. my $json = eval { JSON->new->utf8(0)->decode($data) };
  685. if($@)
  686. {
  687. Log3 $name, 2, "$name: invalid json evaluation on connection check ".$@;
  688. return undef;
  689. }
  690. $hash->{network} = "ok" if($json->{status} eq "ok");
  691. }
  692. return undef;
  693. }
  694. sub
  695. netatmo_connect($)
  696. {
  697. my ($hash) = @_;
  698. netatmo_getToken($hash);
  699. #netatmo_getAppToken($hash);
  700. InternalTimer(gettimeofday()+90, "netatmo_poll", $hash);
  701. }
  702. sub
  703. netatmo_Unban($)
  704. {
  705. my ($hash) = @_;
  706. my $name = $hash->{NAME};
  707. HttpUtils_NonblockingGet({
  708. url => "https://dev.netatmo.com/",
  709. timeout => 20,
  710. noshutdown => 1,
  711. hash => $hash,
  712. type => 'unban',
  713. callback => \&netatmo_parseUnban,
  714. });
  715. return undef;
  716. }
  717. sub
  718. netatmo_parseUnban($$$)
  719. {
  720. my ($param,$err,$data) = @_;
  721. my $hash = $param->{hash};
  722. my $name = $hash->{NAME};
  723. #Log3 $name, 1, "$name unban\n".Dumper($param->{httpheader});
  724. $data =~ /csrf_value: "(.*)"/;
  725. my $csrf_token = $1;
  726. # https://auth.netatmo.com/en-US/access/login?next_url=https://dev.netatmo.com/dev/myaccount
  727. Log3 $name, 1, "$name unban ".$csrf_token;
  728. HttpUtils_NonblockingGet({
  729. url => "https://auth.netatmo.com/en-US/access/login?next_url=https://dev.netatmo.com/dev/myaccount",
  730. timeout => 5,
  731. hash => $hash,
  732. ignoreredirects => 1,
  733. type => 'unban',
  734. header => "Cookie: netatmocomci_csrf_cookie_na=".$csrf_token."; netatmocomlocale=en-US",
  735. data => {ci_csrf_netatmo => $csrf_token, mail => netatmo_decrypt($hash->{helper}{username}), pass => netatmo_decrypt($hash->{helper}{password}), log_submit => 'Log+in', stay_logged => 'accept'},
  736. callback => \&netatmo_parseUnban2,
  737. });
  738. return undef;
  739. }
  740. sub
  741. netatmo_parseUnban2($$$)
  742. {
  743. my ($param,$err,$data) = @_;
  744. my $hash = $param->{hash};
  745. my $name = $hash->{NAME};
  746. Log3 $name, 1, "$name header\n".Dumper($param->{httpheader});
  747. my $header1 = $param->{httpheader};
  748. my $header2 = $param->{httpheader};
  749. my $header3 = $param->{httpheader};
  750. $header1 =~ s/=deleted/x=deleted/g;
  751. $header2 =~ s/=deleted/x=deleted/g;
  752. $header3 =~ s/=deleted/x=deleted/g;
  753. $header1 =~ /Set-Cookie: netatmocomci_csrf_cookie_na=(.*); expires/;
  754. my $csrf_token = $1;
  755. $hash->{helper}{csrf_token} = $csrf_token;
  756. $header2 =~ /Set-Cookie: netatmocomaccess_token=(.*); path/;
  757. my $accesstoken = $1;
  758. $accesstoken =~ s/%7C/|/g;
  759. $hash->{helper}{access_token} = $accesstoken;
  760. $header3 =~ /Set-Cookie: netatmocomrefresh_token=(.*); expires/;
  761. my $refreshtoken = $1;
  762. $hash->{helper}{refresh_token} = $refreshtoken;
  763. Log3 $name, 1, "$name csrftoken ".$csrf_token;
  764. Log3 $name, 1, "$name accesstoken ".$accesstoken;
  765. Log3 $name, 1, "$name refreshtoken ".$refreshtoken;
  766. my $json = '{"application_id":"'.$hash->{helper}{client_id}.'"}';
  767. HttpUtils_NonblockingGet({
  768. url => "https://dev.netatmo.com/api/unbanapp",
  769. timeout => 5,
  770. hash => $hash,
  771. type => 'unban',
  772. header => "Referer: https://dev.netatmo.com/dev/myaccount\r\nAuthorization: Bearer ".$accesstoken."\r\nContent-Type: application/json;charset=utf-8\r\nCookie: netatmocomci_csrf_cookie_na=".$csrf_token."; netatmocomlocale=en-US; netatmocomacces_token=".$accesstoken,
  773. data => $json,
  774. callback => \&netatmo_parseUnban3,
  775. });
  776. return undef;
  777. }
  778. sub
  779. netatmo_parseUnban3($$$)
  780. {
  781. my ($param,$err,$data) = @_;
  782. my $hash = $param->{hash};
  783. my $name = $hash->{NAME};
  784. Log3 $name, 1, "$name header\n".Dumper($param->{httpheader});
  785. Log3 $name, 1, "$name data\n".Dumper($data);
  786. Log3 $name, 1, "$name err\n".Dumper($err);
  787. return undef;
  788. }
  789. sub
  790. netatmo_initDevice($)
  791. {
  792. my ($hash) = @_;
  793. my $name = $hash->{NAME};
  794. AssignIoPort($hash);
  795. if(defined($hash->{IODev}->{NAME})) {
  796. Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
  797. } else {
  798. Log3 $name, 1, "$name: no I/O device";
  799. }
  800. my $device;
  801. if( $hash->{Module} ) {
  802. $device = netatmo_getDeviceDetail( $hash, $hash->{Module} );
  803. } else {
  804. $device = netatmo_getDeviceDetail( $hash, $hash->{Device} );
  805. }
  806. $hash->{stationName} = encode_utf8($device->{station_name}) if( $device->{station_name} );
  807. $hash->{moduleName} = encode_utf8($device->{module_name}) if( $device->{module_name} );
  808. $hash->{name} = encode_utf8($device->{name}) if( $device->{name} );
  809. $hash->{model} = $device->{type} if(defined($device->{type}));
  810. $hash->{firmware} = $device->{firmware} if(defined($device->{firmware}));
  811. $hash->{co2_calibrating} = $device->{co2_calibrating} if(defined($device->{co2_calibrating}));
  812. $hash->{last_upgrade} = FmtDateTime($device->{last_upgrade}) if(defined($device->{last_upgrade}));
  813. $hash->{date_setup} = FmtDateTime($device->{date_setup}) if(defined($device->{date_setup}));
  814. $hash->{last_setup} = FmtDateTime($device->{last_setup}) if(defined($device->{last_setup}));
  815. $hash->{last_status_store} = FmtDateTime($device->{last_status_store}) if(defined($device->{last_status_store}));
  816. $hash->{last_message} = FmtDateTime($device->{last_message}) if(defined($device->{last_message}));
  817. $hash->{last_seen} = FmtDateTime($device->{last_seen}) if(defined($device->{last_seen}));
  818. $hash->{wifi_status} = $device->{wifi_status} if(defined($device->{wifi_status}));
  819. $hash->{rf_status} = $device->{rf_status} if(defined($device->{rf_status}));
  820. #$hash->{battery_percent} = $device->{battery_percent} if(defined($device->{battery_percent}));
  821. $hash->{battery_vp} = $device->{battery_vp} if(defined($device->{battery_vp}));
  822. if( $device->{place} ) {
  823. $hash->{country} = $device->{place}{country};
  824. $hash->{bssid} = $device->{place}{bssid} if(defined($device->{place}{bssid}));
  825. $hash->{altitude} = $device->{place}{altitude} if(defined($device->{place}{altitude}));
  826. $hash->{city} = encode_utf8($device->{place}{geoip_city}) if(defined($device->{place}{geoip_city}));
  827. $hash->{city} = encode_utf8($device->{place}{city}) if(defined($device->{place}{city}));;
  828. $hash->{location} = $device->{place}{location}[1] .",". $device->{place}{location}[0];
  829. }
  830. readingsSingleUpdate($hash, "battery", ($device->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($device->{battery_percent}));
  831. readingsSingleUpdate($hash, "battery_percent", $device->{battery_percent}, 1) if(defined($device->{battery_percent}));
  832. my $state_format;
  833. if( $device->{data_type} ) {
  834. my $newdatatypes = "";
  835. my @reading_names = ();
  836. foreach my $type (@{$device->{data_type}}) {
  837. $newdatatypes = "" if ( !defined($newdatatypes) );
  838. $newdatatypes .= "," if ( $newdatatypes );
  839. $type = "WindStrength,WindAngle,GustStrength,GustAngle" if($type eq "Wind");
  840. $newdatatypes .= $type;
  841. push @reading_names, lc($type);
  842. if( $type eq "Temperature" ) {
  843. $state_format .= " " if( $state_format );
  844. $state_format .= "T: temperature";
  845. } elsif( $type eq "Humidity" ) {
  846. $state_format .= " " if( $state_format );
  847. $state_format .= "H: humidity";
  848. }
  849. }
  850. if($newdatatypes ne "")
  851. {
  852. delete($hash->{dataTypes});
  853. $hash->{dataTypes} = $newdatatypes;
  854. }
  855. $hash->{helper}{readingNames} = \@reading_names;
  856. }
  857. $attr{$name}{stateFormat} = $state_format if( !defined($attr{$name}{stateFormat}) && defined($state_format) && defined($name) );
  858. return undef if(AttrVal($name,"disable",0) eq "1" || !defined($name));
  859. InternalTimer(gettimeofday()+90, "netatmo_poll", $hash);
  860. #netatmo_poll($hash);
  861. }
  862. sub
  863. netatmo_getDevices($;$)
  864. {
  865. my ($hash,$blocking) = @_;
  866. my $name = $hash->{NAME};
  867. netatmo_refreshToken($hash, defined($hash->{access_token}));
  868. Log3 $name, 3, "$name getDevices (devicelist)";
  869. return Log3 $name, 1, "$name: No access token was found! (getDevices)" if(!defined($hash->{access_token}));
  870. if( $blocking ) {
  871. my($err,$data) = HttpUtils_BlockingGet({
  872. url => "https://api.netatmo.com/api/getstationsdata",
  873. timeout => 5,
  874. noshutdown => 1,
  875. data => { access_token => $hash->{access_token}, },
  876. });
  877. netatmo_dispatch( {hash=>$hash,type=>'devicelist'},$err,$data );
  878. return $hash->{helper}{devices};
  879. } else {
  880. HttpUtils_NonblockingGet({
  881. url => "https://api.netatmo.com/api/getstationsdata",
  882. timeout => 20,
  883. noshutdown => 1,
  884. data => { access_token => $hash->{access_token}, },
  885. hash => $hash,
  886. type => 'devicelist',
  887. callback => \&netatmo_dispatch,
  888. });
  889. }
  890. }
  891. sub
  892. netatmo_getHomes($;$)
  893. {
  894. my ($hash,$blocking) = @_;
  895. my $name = $hash->{NAME};
  896. netatmo_refreshToken($hash, defined($hash->{access_token}));
  897. Log3 $name, 3, "$name getHomes (homelist)";
  898. return Log3 $name, 1, "$name: No access token was found! (getHomes)" if(!defined($hash->{access_token}));
  899. if( $blocking ) {
  900. my($err,$data) = HttpUtils_BlockingGet({
  901. url => "https://api.netatmo.com/api/gethomedata",
  902. timeout => 5,
  903. noshutdown => 1,
  904. data => { access_token => $hash->{access_token}, },
  905. });
  906. netatmo_dispatch( {hash=>$hash,type=>'homelist'},$err,$data );
  907. return $hash->{helper}{homes};
  908. } else {
  909. HttpUtils_NonblockingGet({
  910. url => "https://api.netatmo.com/api/gethomedata",
  911. timeout => 20,
  912. noshutdown => 1,
  913. data => { access_token => $hash->{access_token}, },
  914. hash => $hash,
  915. type => 'homelist',
  916. callback => \&netatmo_dispatch,
  917. });
  918. }
  919. }
  920. sub
  921. netatmo_getThermostats($;$)
  922. {
  923. my ($hash,$blocking) = @_;
  924. my $name = $hash->{NAME};
  925. netatmo_refreshToken($hash, defined($hash->{access_token}));
  926. Log3 $name, 3, "$name getThermostats (thermostatlist)";
  927. return Log3 $name, 1, "$name: No access token was found! (getThermostats)" if(!defined($hash->{access_token}));
  928. if( $blocking ) {
  929. my($err,$data) = HttpUtils_BlockingGet({
  930. url => "https://api.netatmo.com/api/getthermostatsdata",
  931. timeout => 5,
  932. noshutdown => 1,
  933. data => { access_token => $hash->{access_token}, },
  934. });
  935. netatmo_dispatch( {hash=>$hash,type=>'thermostatlist'},$err,$data );
  936. return $hash->{helper}{thermostats};
  937. } else {
  938. HttpUtils_NonblockingGet({
  939. url => "https://api.netatmo.com/api/getthermostatsdata",
  940. timeout => 20,
  941. noshutdown => 1,
  942. data => { access_token => $hash->{access_token}, },
  943. hash => $hash,
  944. type => 'thermostatlist',
  945. callback => \&netatmo_dispatch,
  946. });
  947. }
  948. }
  949. sub
  950. netatmo_getHomecoachs($;$)
  951. {
  952. my ($hash,$blocking) = @_;
  953. my $name = $hash->{NAME};
  954. netatmo_refreshToken($hash, defined($hash->{access_token}));
  955. Log3 $name, 3, "$name getHomecoachs (homecoachlist)";
  956. return Log3 $name, 1, "$name: No access token was found! (getHomecoachs)" if(!defined($hash->{access_token}));
  957. if( $blocking ) {
  958. my($err,$data) = HttpUtils_BlockingGet({
  959. url => "https://api.netatmo.com/api/gethomecoachsdata",
  960. timeout => 5,
  961. noshutdown => 1,
  962. data => { access_token => $hash->{access_token}, },
  963. });
  964. netatmo_dispatch( {hash=>$hash,type=>'homecoachlist'},$err,$data );
  965. return $hash->{helper}{homecoachs};
  966. } else {
  967. HttpUtils_NonblockingGet({
  968. url => "https://api.netatmo.com/api/gethomecoachsdata",
  969. timeout => 20,
  970. noshutdown => 1,
  971. data => { access_token => $hash->{access_token}, },
  972. hash => $hash,
  973. type => 'homecoachlist',
  974. callback => \&netatmo_dispatch,
  975. });
  976. }
  977. }
  978. sub
  979. netatmo_pingCamera($;$)
  980. {
  981. my ($hash,$blocking) = @_;
  982. my $name = $hash->{NAME};
  983. my $iohash = $hash->{IODev};
  984. netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  985. return Log3 $name, 1, "$name: No access token was found! (pingCamera)" if(!defined($iohash->{access_token}));
  986. my $pingurl = ReadingsVal( $name, "vpn_url", undef );
  987. return undef if(!defined($pingurl));
  988. Log3 $name, 3, "$name pingCamera (cameraping)";
  989. $pingurl .= "/command/ping";
  990. Log3 $name, 5, "$name pingCamera ".$pingurl;
  991. if( $blocking ) {
  992. my($err,$data) = HttpUtils_BlockingGet({
  993. url => $pingurl,
  994. timeout => 10,
  995. sslargs => { SSL_hostname => '', },
  996. data => { access_token => $iohash->{access_token}, },
  997. });
  998. netatmo_dispatch( {hash=>$hash,type=>'cameraping'},$err,$data );
  999. return undef;
  1000. } else {
  1001. HttpUtils_NonblockingGet({
  1002. url => $pingurl,
  1003. timeout => 10,
  1004. sslargs => { SSL_hostname => '', },
  1005. data => { access_token => $iohash->{access_token}, },
  1006. hash => $hash,
  1007. type => 'cameraping',
  1008. callback => \&netatmo_dispatch,
  1009. });
  1010. }
  1011. }
  1012. sub
  1013. netatmo_getCameraVideo($$;$)
  1014. {
  1015. my ($hash,$videoid,$local) = @_;
  1016. my $name = $hash->{NAME};
  1017. $local = ($local eq "video_local" ? "_local" : "");
  1018. #my $iohash = $hash->{IODev};
  1019. #netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1020. my $commandurl = ReadingsVal( $name, "local_url", undef);
  1021. if(!defined($commandurl)) {
  1022. ReadingsVal( $name, "vpn_url", undef );
  1023. } else {
  1024. $local = "";
  1025. }
  1026. return undef if(!defined($commandurl));
  1027. my $quality = AttrVal($name,"videoquality","medium");
  1028. $commandurl .= "/vod/".$videoid."/files/".$quality."/index".$local.".m3u8";
  1029. Log3 $name, 3, "$name getCameraVideo ".$commandurl;
  1030. # HttpUtils_BlockingGet({
  1031. # url => $cmdurl,
  1032. # noshutdown => 1,
  1033. # data => { access_token => $iohash->{access_token}, },
  1034. # hash => $hash,
  1035. # type => 'cameravideo',
  1036. # callback => \&netatmo_dispatch,
  1037. # });
  1038. return $commandurl;
  1039. }
  1040. sub
  1041. netatmo_getCameraLive($;$)
  1042. {
  1043. my ($hash,$local) = @_;
  1044. my $name = $hash->{NAME};
  1045. $local = ($local eq "live_local" ? "_local" : "");
  1046. #my $iohash = $hash->{IODev};
  1047. #netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1048. my $commandurl = ReadingsVal( $name, "local_url", undef);
  1049. if(!defined($commandurl)) {
  1050. ReadingsVal( $name, "vpn_url", undef );
  1051. } else {
  1052. $local = "";
  1053. }
  1054. return undef if(!defined($commandurl));
  1055. my $quality = AttrVal($name,"videoquality","medium");
  1056. $commandurl .= "/live/files/".$quality."/index".$local.".m3u8";
  1057. Log3 $name, 3, "$name getCameraLive ".$commandurl;
  1058. # HttpUtils_BlockingGet({
  1059. # url => $cmdurl,
  1060. # noshutdown => 1,
  1061. # data => { access_token => $iohash->{access_token}, },
  1062. # hash => $hash,
  1063. # type => 'cameravideo',
  1064. # callback => \&netatmo_dispatch,
  1065. # });
  1066. return $commandurl;
  1067. }
  1068. sub
  1069. netatmo_getCameraTimelapse($)
  1070. {
  1071. my ($hash) = @_;
  1072. my $name = $hash->{NAME};
  1073. #my $iohash = $hash->{IODev};
  1074. #netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1075. my $cmdurl = ReadingsVal( $name, "local_url", undef );
  1076. return undef if(!defined($cmdurl));
  1077. $cmdurl .= "/command/dl/timelapse";
  1078. Log3 $name, 3, "$name getCameraTimelapse ".$cmdurl;
  1079. # HttpUtils_BlockingGet({
  1080. # url => $cmdurl,
  1081. # noshutdown => 1,
  1082. # data => { access_token => $iohash->{access_token}, },
  1083. # hash => $hash,
  1084. # type => 'cameravideo',
  1085. # callback => \&netatmo_dispatch,
  1086. # });
  1087. return $cmdurl;
  1088. }
  1089. sub
  1090. netatmo_getCameraSnapshot($;$)
  1091. {
  1092. my ($hash,$local) = @_;
  1093. my $name = $hash->{NAME};
  1094. #my $iohash = $hash->{IODev};
  1095. #netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1096. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1097. return undef if(!defined($commandurl));
  1098. $commandurl .= "/live/snapshot_720.jpg";
  1099. Log3 $name, 3, "$name getCameraSnapshot ".$commandurl;
  1100. # HttpUtils_BlockingGet({
  1101. # url => $cmdurl,
  1102. # noshutdown => 1,
  1103. # data => { access_token => $iohash->{access_token}, },
  1104. # hash => $hash,
  1105. # type => 'cameravideo',
  1106. # callback => \&netatmo_dispatch,
  1107. # });
  1108. return $commandurl;
  1109. }
  1110. sub
  1111. netatmo_getEvents($)
  1112. {
  1113. my ($hash) = @_;
  1114. my $name = $hash->{NAME};
  1115. my $iohash = $hash->{IODev};
  1116. netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1117. Log3 $name, 3, "$name getEvents (homeevents)";
  1118. return Log3 $name, 1, "$name: No access token was found! (getEvents)" if(!defined($iohash->{access_token}));
  1119. HttpUtils_NonblockingGet({
  1120. url => "https://api.netatmo.com/api/getnextevents",
  1121. timeout => 20,
  1122. noshutdown => 1,
  1123. data => { access_token => $iohash->{access_token}, home_id => $hash->{Home}, event_id => $hash->{lastevent}, },
  1124. hash => $hash,
  1125. type => 'homeevents',
  1126. callback => \&netatmo_dispatch,
  1127. });
  1128. }
  1129. sub
  1130. netatmo_getPublicDevices($$;$$$$)
  1131. {
  1132. my ($hash,$blocking,$lat1,$lon1,$lat2,$lon2) = @_;
  1133. my $name = $hash->{NAME};
  1134. my $iohash = $hash->{IODev};
  1135. $iohash = $hash if( !defined($iohash) );
  1136. #Log3 $name, 5, "$name getPublicDevices $lat1,$lon1,$lat2,$lon2";
  1137. if( !defined($lon1) ) {
  1138. my $s = $lat1;
  1139. $s = 0.025 if ( !defined($s) );
  1140. my $lat = AttrVal("global","latitude", 50.112);
  1141. my $lon = AttrVal("global","longitude", 8.686);
  1142. $lat1 = $lat + $s;
  1143. $lon1 = $lon + $s;
  1144. $lat2 = $lat - $s;
  1145. $lon2 = $lon - $s;
  1146. } elsif( !defined($lon2) ) {
  1147. my $lat = $lat1;
  1148. my $lon = $lon1;
  1149. my $s = $lat2;
  1150. $s = 0.025 if ( !defined($s) );
  1151. $lat1 = $lat + $s;
  1152. $lon1 = $lon + $s;
  1153. $lat2 = $lat - $s;
  1154. $lon2 = $lon - $s;
  1155. }
  1156. my $lat_ne = ($lat1 > $lat2) ? $lat1 : $lat2;
  1157. my $lon_ne = ($lon1 > $lon2) ? $lon1 : $lon2;
  1158. my $lat_sw = ($lat1 > $lat2) ? $lat2 : $lat1;
  1159. my $lon_sw = ($lon1 > $lon2) ? $lon2 : $lon1;
  1160. Log3 $name, 3, "$name getPublicDevices ($lat_ne,$lon_ne / $lat_sw,$lon_sw)";
  1161. netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  1162. return Log3 $name, 1, "$name: No access token was found! (getPublicDevices)" if(!defined($iohash->{access_token}));
  1163. if( $blocking ) {
  1164. my($err,$data) = HttpUtils_BlockingGet({
  1165. url => "https://api.netatmo.com/api/getpublicdata",
  1166. timeout => 5,
  1167. noshutdown => 1,
  1168. data => { access_token => $iohash->{access_token}, lat_ne => $lat_ne, lon_ne => $lon_ne, lat_sw => $lat_sw, lon_sw => $lon_sw },
  1169. });
  1170. return netatmo_dispatch( {hash=>$hash,type=>'publicdata'},$err,$data );
  1171. } else {
  1172. HttpUtils_NonblockingGet({
  1173. url => "https://api.netatmo.com/api/getpublicdata",
  1174. timeout => 20,
  1175. noshutdown => 1,
  1176. data => { access_token => $iohash->{access_token}, lat_ne => $lat_ne, lon_ne => $lon_ne, lat_sw => $lat_sw, lon_sw => $lon_sw, filter => 'true' },
  1177. hash => $hash,
  1178. type => 'publicdata',
  1179. callback => \&netatmo_dispatch,
  1180. });
  1181. }
  1182. }
  1183. sub
  1184. netatmo_getAddress($$$$)
  1185. {
  1186. my ($hash,$blocking,$lat,$lon) = @_;
  1187. my $name = $hash->{NAME};
  1188. my $iohash = $hash->{IODev};
  1189. $iohash = $hash if( !defined($iohash) );
  1190. Log3 $name, 5, "$name getAddress ($lat,$lon)";
  1191. if( $blocking ) {
  1192. my($err,$data) = HttpUtils_BlockingGet({
  1193. url => "https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon",
  1194. noshutdown => 1,
  1195. });
  1196. return netatmo_dispatch( {hash=>$hash,type=>'address'},$err,$data );
  1197. } else {
  1198. HttpUtils_NonblockingGet({
  1199. url => "https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon",
  1200. noshutdown => 1,
  1201. hash => $hash,
  1202. type => 'address',
  1203. callback => \&netatmo_dispatch,
  1204. });
  1205. }
  1206. }
  1207. sub
  1208. netatmo_getLatLong($$$)
  1209. {
  1210. my ($hash,$blocking,$addr) = @_;
  1211. my $name = $hash->{NAME};
  1212. my $iohash = $hash->{IODev};
  1213. $iohash = $hash if( !defined($iohash) );
  1214. Log3 $name, 5, "$name getLatLong ($addr)";
  1215. if( $blocking ) {
  1216. my($err,$data) = HttpUtils_BlockingGet({
  1217. url => "https://maps.googleapis.com/maps/api/geocode/json?address=germany+$addr",
  1218. noshutdown => 1,
  1219. });
  1220. return netatmo_dispatch( {hash=>$hash,type=>'latlng'},$err,$data );
  1221. } else {
  1222. HttpUtils_NonblockingGet({
  1223. url => "https://maps.googleapis.com/maps/api/geocode/json?address=germany+$addr",
  1224. noshutdown => 1,
  1225. hash => $hash,
  1226. type => 'latlng',
  1227. callback => \&netatmo_dispatch,
  1228. });
  1229. }
  1230. }
  1231. sub
  1232. netatmo_getDeviceDetail($$)
  1233. {
  1234. my ($hash,$id) = @_;
  1235. my $name = $hash->{NAME};
  1236. $hash = $hash->{IODev} if( defined($hash->{IODev}) );
  1237. Log3 $name, 5, "$name getDeviceDetail ($id)";
  1238. netatmo_getDevices($hash,1) if( !$hash->{helper}{devices} );
  1239. netatmo_getHomecoachs($hash,1) if( !$hash->{helper}{homecoachs} );
  1240. foreach my $device (@{$hash->{helper}{devices}}) {
  1241. return $device if( $device->{_id} eq $id );
  1242. }
  1243. foreach my $device (@{$hash->{helper}{homecoachs}}) {
  1244. return $device if( $device->{_id} eq $id );
  1245. }
  1246. return undef;
  1247. }
  1248. sub
  1249. netatmo_requestDeviceReadings($@)
  1250. {
  1251. my ($hash,$id,$type,$module) = @_;
  1252. my $name = $hash->{NAME};
  1253. return undef if( !defined($hash->{IODev}) );
  1254. my $iohash = $hash->{IODev};
  1255. $type = $hash->{dataTypes} if( !$type );
  1256. $type = "Temperature,CO2,Humidity,Noise,Pressure,health_idx" if( !$type && $hash->{SUBTYPE} eq "DEVICE" );
  1257. $type = "Temperature,CO2,Humidity,Noise,Pressure,Rain,WindStrength,WindAngle,GustStrength,GustAngle,Sp_Temperature,BoilerOn,BoilerOff,health_idx" if( !$type );
  1258. $type = "WindAngle,WindStrength,GustStrength,GustAngle" if ($type eq "Wind");
  1259. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1260. return Log3 $name, 1, "$name: No access token was found! (requestDeviceReadings)" if(!defined($iohash->{access_token}));
  1261. my %data = (access_token => $iohash->{access_token}, device_id => $id, scale => "max", type => $type);
  1262. $data{"module_id"} = $module if( $module );
  1263. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  1264. $data{"date_begin"} = $lastupdate if( defined($lastupdate) );
  1265. Log3 $name, 3, "$name: requestDeviceReadings ($type)";
  1266. HttpUtils_NonblockingGet({
  1267. url => "https://api.netatmo.com/api/getmeasure",
  1268. timeout => 20,
  1269. noshutdown => 1,
  1270. data => \%data,
  1271. hash => $hash,
  1272. type => 'getmeasure',
  1273. requested => $type,
  1274. callback => \&netatmo_dispatch,
  1275. });
  1276. }
  1277. sub
  1278. netatmo_initHome($@)
  1279. {
  1280. my ($hash) = @_;
  1281. my $name = $hash->{NAME};
  1282. return undef if( !defined($hash->{IODev}) );
  1283. my $iohash = $hash->{IODev};
  1284. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1285. return Log3 $name, 1, "$name: No access token was found! (initHome)" if(!defined($iohash->{access_token}));
  1286. my %data = (access_token => $iohash->{access_token}, home_id => $hash->{Home});
  1287. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  1288. #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) );
  1289. Log3 $name, 3, "$name initHome (gethomedata)";
  1290. # url => "https://api.netatmo.com/api/gethomedata",
  1291. # data => \%data,
  1292. HttpUtils_NonblockingGet({
  1293. url => "https://app.netatmo.net/api/gethomesdata",
  1294. timeout => 20,
  1295. noshutdown => 1,
  1296. header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token_app},
  1297. hash => $hash,
  1298. type => 'gethomedata',
  1299. callback => \&netatmo_dispatch,
  1300. });
  1301. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "netatmo_poll", $hash);
  1302. $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{INTERVAL};
  1303. }
  1304. sub
  1305. netatmo_requestHomeReadings($@)
  1306. {
  1307. my ($hash,$id) = @_;
  1308. my $name = $hash->{NAME};
  1309. return undef if( !defined($hash->{IODev}) );
  1310. my $iohash = $hash->{IODev};
  1311. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1312. return undef if(!defined($iohash->{access_token}));
  1313. netatmo_refreshAppToken( $iohash, defined($iohash->{access_token_app}) );
  1314. return undef if(!defined($iohash->{access_token_app}));
  1315. my %data = (access_token => $iohash->{access_token}, home_id => $id, size => 50);
  1316. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  1317. #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) );
  1318. Log3 $name, 3, "$name requestHomeReadings (gethomedata)";
  1319. # url => "https://api.netatmo.com/api/gethomedata",
  1320. # data => \%data,
  1321. HttpUtils_NonblockingGet({
  1322. url => "https://app.netatmo.net/api/gethomesdata",
  1323. timeout => 20,
  1324. noshutdown => 1,
  1325. header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token_app},
  1326. hash => $hash,
  1327. type => 'gethomedata',
  1328. callback => \&netatmo_dispatch,
  1329. });
  1330. }
  1331. sub
  1332. netatmo_requestThermostatReadings($@)
  1333. {
  1334. my ($hash,$id) = @_;
  1335. my $name = $hash->{NAME};
  1336. return undef if( !defined($hash->{IODev}) );
  1337. Log3 $name, 3, "$name: requestThermostatReadings ($id)";
  1338. my $iohash = $hash->{IODev};
  1339. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1340. return Log3 $name, 1, "$name: No access token was found! (requestThermostatReadings)" if(!defined($iohash->{access_token}));
  1341. my %data = (access_token => $iohash->{access_token}, device_id => $id);
  1342. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  1343. #$data{"size"} = 1;#$lastupdate if( defined($lastupdate) );
  1344. HttpUtils_NonblockingGet({
  1345. url => "https://api.netatmo.com/api/getthermostatsdata",
  1346. timeout => 20,
  1347. noshutdown => 1,
  1348. data => \%data,
  1349. hash => $hash,
  1350. type => 'getthermostatsdata',
  1351. callback => \&netatmo_dispatch,
  1352. });
  1353. }
  1354. sub
  1355. netatmo_requestPersonReadings($)
  1356. {
  1357. my ($hash) = @_;
  1358. my $name = $hash->{NAME};
  1359. return undef if( !defined($hash->{IODev}) );
  1360. return undef if( !defined($hash->{Home}) );
  1361. my $iohash = $hash->{IODev};
  1362. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1363. return Log3 $name, 1, "$name: No access token was found! (requestPersonReadings)" if(!defined($iohash->{access_token}));
  1364. Log3 $name, 3, "$name: requestPersonReadings (getpersondata)";
  1365. my %data = (access_token => $iohash->{access_token}, home_id => $hash->{Home}, person_id => $hash->{Person}, offset => '20');
  1366. my $lastupdate = ReadingsVal( $name, ".lastupdate", undef );
  1367. HttpUtils_NonblockingGet({
  1368. url => "https://api.netatmo.com/api/getlasteventof",
  1369. timeout => 20,
  1370. noshutdown => 1,
  1371. data => \%data,
  1372. hash => $hash,
  1373. type => 'getpersondata',
  1374. callback => \&netatmo_dispatch,
  1375. });
  1376. }
  1377. sub
  1378. netatmo_setPresence($$)
  1379. {
  1380. my ($hash,$status) = @_;
  1381. my $name = $hash->{NAME};
  1382. return undef if( !defined($hash->{IODev}) );
  1383. my $iohash = $hash->{IODev};
  1384. netatmo_refreshAppToken($iohash, defined($iohash->{access_token_app}));
  1385. return Log3 $name, 1, "$name: No access token was found! (setPresence)" if(!defined($iohash->{access_token_app}));
  1386. my $personid = $hash->{Person};
  1387. my $urlstatus = $status;
  1388. my $json;
  1389. if($status eq "home")
  1390. {
  1391. $json = '{"home_id":"'.$hash->{Home}.'","person_ids":["'.$hash->{Person}.'"]}';
  1392. }
  1393. elsif($status eq "away")
  1394. {
  1395. $json = '{"home_id":"'.$hash->{Home}.'","person_id":"'.$hash->{Person}.'"}';
  1396. }
  1397. elsif($status eq "empty")
  1398. {
  1399. $json = '{"home_id":"'.$hash->{Home}.'"}';
  1400. $urlstatus = "away";
  1401. }
  1402. Log3 $name, 5, "$name: setPresence ($status)";
  1403. HttpUtils_NonblockingGet({
  1404. url => "https://app.netatmo.net/api/setpersons".$urlstatus,
  1405. timeout => 20,
  1406. noshutdown => 1,
  1407. method => "POST",
  1408. header => "Content-Type: application/json\r\nAuthorization: Bearer ".$iohash->{access_token_app},
  1409. data => $json,
  1410. hash => $hash,
  1411. type => 'setpersonsstatus_'.$status,
  1412. callback => \&netatmo_dispatch,
  1413. });
  1414. }
  1415. sub
  1416. netatmo_setNotifications($$$)
  1417. {
  1418. my ($hash,$setting,$value) = @_;
  1419. my $name = $hash->{NAME};
  1420. return undef if( !defined($hash->{IODev}) );
  1421. my $iohash = $hash->{IODev};
  1422. netatmo_refreshAppToken($iohash, defined($iohash->{access_token_app}));
  1423. return Log3 $name, 1, "$name: No access token was found! (setNotifications)" if(!defined($iohash->{access_token_app}));
  1424. if( !defined($iohash->{csrf_token}) )
  1425. {
  1426. my($err0,$data0) = HttpUtils_BlockingGet({
  1427. url => "https://auth.netatmo.com/access/checklogin",
  1428. timeout => 10,
  1429. noshutdown => 1,
  1430. });
  1431. if($err0 || !defined($data0))
  1432. {
  1433. Log3 $name, 1, "$name: csrf call failed! ".$err0;
  1434. return undef;
  1435. }
  1436. $data0 =~ /ci_csrf_netatmo" value="(.*)"/;
  1437. my $tmptoken = $1;
  1438. $iohash->{csrf_token} = $tmptoken;
  1439. if(!defined($iohash->{csrf_token})) {
  1440. Log3 $name, 1, "$name: CSRF ERROR ";
  1441. return undef;
  1442. }
  1443. Log3 $name, 4, "$name: csrf_token ".$iohash->{csrf_token};
  1444. }
  1445. my $homeid = $hash->{Home};
  1446. my %data;
  1447. if($setting eq "presence_notify_from" || $setting eq "presence_notify_to" || $setting eq "gone_after")
  1448. {
  1449. my @timevalue = split(":",$value);
  1450. if(defined($timevalue[1]))
  1451. {
  1452. $value = int($timevalue[0])*3600 + int($timevalue[1])*60;
  1453. }
  1454. else
  1455. {
  1456. $value *= 60;
  1457. }
  1458. $value = 0 if($value < 0);
  1459. $value = 86400 if($value > 86400 && $setting ne "gone_after");
  1460. }
  1461. elsif($setting eq "smart_notifs")
  1462. {
  1463. $value = (($value eq "on") ? "true" : "false");
  1464. }
  1465. if($setting eq "presence_enable_notify_from_to" || $setting =~ /^presence_record_/ || $setting =~ /^presence_notify_/ )
  1466. {
  1467. %data = (home_id => $homeid, 'presence_settings['.$setting.']' => $value, ci_csrf_netatmo => $iohash->{csrf_token});
  1468. }
  1469. else
  1470. {
  1471. %data = (home_id => $homeid, $setting => $value, ci_csrf_netatmo => $iohash->{csrf_token});
  1472. }
  1473. Log3 $name, 5, "$name: setNotifications ($setting $value)";
  1474. HttpUtils_NonblockingGet({
  1475. url => "https://my.netatmo.com/api/updatehome",
  1476. timeout => 20,
  1477. noshutdown => 1,
  1478. method => "POST",
  1479. header => "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\nAuthorization: Bearer ".$iohash->{access_token_app},
  1480. data => \%data,
  1481. hash => $hash,
  1482. type => 'sethomesettings',
  1483. callback => \&netatmo_dispatch,
  1484. });
  1485. }
  1486. sub
  1487. netatmo_setCamera($$$)
  1488. {
  1489. my ($hash,$status,$pin) = @_;
  1490. my $name = $hash->{NAME};
  1491. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1492. return undef if(!defined($commandurl));
  1493. $commandurl .= "/command/changestatus?status=$status&pin=$pin";
  1494. Log3 $name, 3, "$name: setCamera ".$commandurl;
  1495. HttpUtils_NonblockingGet({
  1496. url => $commandurl,
  1497. timeout => 20,
  1498. noshutdown => 1,
  1499. verify_hostname => 0,
  1500. hash => $hash,
  1501. type => 'camerastatus',
  1502. callback => \&netatmo_dispatch,
  1503. });
  1504. }
  1505. sub
  1506. netatmo_setCameraSetting($$$)
  1507. {
  1508. my ($hash,$setting,$newvalue) = @_;
  1509. my $name = $hash->{NAME};
  1510. #netatmo_pingCamera( $hash );
  1511. my $commandurl = ReadingsVal( $name, "vpn_url", undef );
  1512. return undef if(!defined($commandurl));
  1513. $commandurl .= "/command/changesetting?$setting=$newvalue";
  1514. Log3 $name, 3, "$name: setCameraSetting ".$commandurl;
  1515. HttpUtils_NonblockingGet({
  1516. url => $commandurl,
  1517. timeout => 20,
  1518. noshutdown => 1,
  1519. verify_hostname => 0,
  1520. hash => $hash,
  1521. type => 'camerastatus',
  1522. callback => \&netatmo_dispatch,
  1523. });
  1524. }
  1525. sub
  1526. netatmo_setFloodlight($$)
  1527. {
  1528. my ($hash,$setting) = @_;
  1529. my $name = $hash->{NAME};
  1530. #netatmo_pingCamera( $hash );
  1531. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1532. return undef if(!defined($commandurl));
  1533. $commandurl .= "/command/floodlight_set_config?config=%7B%22mode%22:%22$setting%22%7D";
  1534. Log3 $name, 3, "$name: setFloodlight ".$commandurl;
  1535. HttpUtils_NonblockingGet({
  1536. url => $commandurl,
  1537. timeout => 20,
  1538. noshutdown => 1,
  1539. verify_hostname => 0,
  1540. hash => $hash,
  1541. type => 'camerastatus',
  1542. callback => \&netatmo_dispatch,
  1543. });
  1544. }
  1545. sub
  1546. netatmo_setIntensity($$)
  1547. {
  1548. my ($hash,$setting) = @_;
  1549. my $name = $hash->{NAME};
  1550. #netatmo_pingCamera( $hash );
  1551. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1552. return undef if(!defined($commandurl));
  1553. $commandurl .= "/command/floodlight_interactive_config?intensity=$setting";
  1554. Log3 $name, 3, "$name: setIntensity ".$commandurl;
  1555. HttpUtils_NonblockingGet({
  1556. url => $commandurl,
  1557. timeout => 20,
  1558. noshutdown => 1,
  1559. verify_hostname => 0,
  1560. hash => $hash,
  1561. type => 'camerastatus',
  1562. callback => \&netatmo_dispatch,
  1563. });
  1564. }
  1565. sub
  1566. netatmo_setPresenceConfig($$)
  1567. {
  1568. my ($hash,$setting) = @_;
  1569. my $name = $hash->{NAME};
  1570. #netatmo_pingCamera( $hash );
  1571. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1572. return undef if(!defined($commandurl));
  1573. $commandurl .= "/command/floodlight_set_config?config=%7B%22intensity%22:".ReadingsVal( $name, "intensity", 50 ).",%22night%22:%7B%22always%22:".ReadingsVal( $name, "night_always", "false" ).",%22animal%22:".ReadingsVal( $name, "night_animal", "false" ).",%22movement%22:".ReadingsVal( $name, "night_movement", "false" ).",%22person%22:".ReadingsVal( $name, "night_person", "true" ).",%22vehicle%22:".ReadingsVal( $name, "night_vehicle", "false" )."%7D%7D";
  1574. Log3 $name, 3, "$name: setPresenceConfig ".$commandurl;
  1575. HttpUtils_NonblockingGet({
  1576. url => $commandurl,
  1577. timeout => 20,
  1578. noshutdown => 1,
  1579. verify_hostname => 0,
  1580. hash => $hash,
  1581. type => 'camerastatus',
  1582. callback => \&netatmo_dispatch,
  1583. });
  1584. }
  1585. sub
  1586. netatmo_getPresenceConfig($)
  1587. {
  1588. my ($hash) = @_;
  1589. my $name = $hash->{NAME};
  1590. #netatmo_pingCamera( $hash );
  1591. my $commandurl = ReadingsVal( $name, "local_url", ReadingsVal( $name, "vpn_url", undef ) );
  1592. return undef if(!defined($commandurl));
  1593. $commandurl .= "/command/floodlight_get_config";
  1594. Log3 $name, 3, "$name: getPresenceConfig ".$commandurl;
  1595. HttpUtils_NonblockingGet({
  1596. url => $commandurl,
  1597. timeout => 20,
  1598. noshutdown => 1,
  1599. verify_hostname => 0,
  1600. hash => $hash,
  1601. type => 'cameraconfig',
  1602. callback => \&netatmo_dispatch,
  1603. });
  1604. }
  1605. sub
  1606. netatmo_setTagCalibration($$)
  1607. {
  1608. my ($hash,$setting) = @_;
  1609. my $name = $hash->{NAME};
  1610. return undef if( !defined($hash->{Camera}) );
  1611. my $camerahash = $modules{$hash->{TYPE}}{defptr}{"C$hash->{Camera}"};
  1612. return undef if( !defined($camerahash));
  1613. #netatmo_pingCamera( $hash );
  1614. my $commandurl = ReadingsVal( $camerahash->{NAME}, "local_url", ReadingsVal( $camerahash->{NAME}, "vpn_url", undef ) );
  1615. return undef if(!defined($commandurl));
  1616. $commandurl .= "/command/dtg_cal?id=".$hash->{Tag};
  1617. Log3 $name, 3, "$name: setTagCalibration ".$commandurl;
  1618. HttpUtils_NonblockingGet({
  1619. url => $commandurl,
  1620. timeout => 20,
  1621. noshutdown => 1,
  1622. verify_hostname => 0,
  1623. hash => $hash,
  1624. type => 'tagstatus',
  1625. callback => \&netatmo_dispatch,
  1626. });
  1627. }
  1628. sub
  1629. netatmo_setThermostatMode($$;$$)
  1630. {
  1631. my ($hash,$set,$duration) = @_;
  1632. my $name = $hash->{NAME};
  1633. return undef if( !defined($hash->{IODev}) );
  1634. my $iohash = $hash->{IODev};
  1635. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1636. return Log3 $name, 1, "$name: No access token was found! (setThermostatMode)" if(!defined($iohash->{access_token}));
  1637. my %data;
  1638. %data = (access_token => $iohash->{access_token}, device_id => $hash->{Relay}, module_id => $hash->{Thermostat}, setpoint_mode => $set);
  1639. if(defined($duration) || $set eq "max")
  1640. {
  1641. $duration = AttrVal($name,"setpoint_duration",60) if(!defined($duration));
  1642. my $endpoint = time + (60 * $duration);
  1643. %data = (access_token => $iohash->{access_token}, device_id => $hash->{Relay}, module_id => $hash->{Thermostat}, setpoint_mode => $set, setpoint_endtime => $endpoint);
  1644. }
  1645. Log3 $name, 3, "$name: setThermostatMode ($set)";
  1646. HttpUtils_NonblockingGet({
  1647. url => 'https://api.netatmo.com/api/setthermpoint',
  1648. timeout => 20,
  1649. noshutdown => 1,
  1650. data => \%data,
  1651. hash => $hash,
  1652. type => 'setthermostat',
  1653. callback => \&netatmo_dispatch,
  1654. });
  1655. }
  1656. sub
  1657. netatmo_setThermostatTemp($$;$$)
  1658. {
  1659. my ($hash,$set,$duration) = @_;
  1660. my $name = $hash->{NAME};
  1661. return undef if( !defined($hash->{IODev}) );
  1662. my $iohash = $hash->{IODev};
  1663. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1664. return Log3 $name, 1, "$name: No access token was found! (setThermostatTemp)" if(!defined($iohash->{access_token}));
  1665. $duration = AttrVal($name,"setpoint_duration",60) if(!defined($duration));
  1666. my $endpoint = time + (60 * $duration);
  1667. my %data = (access_token => $iohash->{access_token}, device_id => $hash->{Relay}, module_id => $hash->{Thermostat}, setpoint_mode => 'manual', setpoint_temp => $set, setpoint_endtime => $endpoint);
  1668. Log3 $name, 3, "$name: setThermostatTemp ($set)";
  1669. HttpUtils_NonblockingGet({
  1670. url => 'https://api.netatmo.com/api/setthermpoint',
  1671. timeout => 20,
  1672. noshutdown => 1,
  1673. data => \%data,
  1674. hash => $hash,
  1675. type => 'setthermostat',
  1676. callback => \&netatmo_dispatch,
  1677. });
  1678. }
  1679. sub
  1680. netatmo_setThermostatProgram($$)
  1681. {
  1682. my ($hash,$set) = @_;
  1683. my $name = $hash->{NAME};
  1684. return undef if( !defined($hash->{IODev}) );
  1685. my $iohash = $hash->{IODev};
  1686. netatmo_refreshToken( $iohash, defined($iohash->{access_token}) );
  1687. return Log3 $name, 1, "$name: No access token was found! (setThermostatProgram)" if(!defined($iohash->{access_token}));
  1688. my $schedule_id = 0;
  1689. foreach my $scheduledata ( @{$hash->{schedules}})
  1690. {
  1691. $schedule_id = @{$scheduledata}[1] if($set eq @{$scheduledata}[0]);
  1692. }
  1693. my %data = (access_token => $iohash->{access_token}, device_id => $hash->{Relay}, module_id => $hash->{Thermostat}, schedule_id => $schedule_id);
  1694. Log3 $name, 3, "$name: setThermostatProgram ($set / $schedule_id)";
  1695. HttpUtils_NonblockingGet({
  1696. url => 'https://api.netatmo.com/api/switchschedule',
  1697. timeout => 20,
  1698. noshutdown => 1,
  1699. data => \%data,
  1700. hash => $hash,
  1701. type => 'setthermostat',
  1702. callback => \&netatmo_dispatch,
  1703. });
  1704. }
  1705. sub
  1706. netatmo_poll($)
  1707. {
  1708. my ($hash) = @_;
  1709. my $name = $hash->{NAME};
  1710. return undef if(AttrVal($name,"disable",0) eq "1" || !defined($name));
  1711. # my $resolve = inet_aton("api.netatmo.com");
  1712. # if(!defined($resolve))
  1713. # {
  1714. # Log3 $name, 1, "$name: DNS error on poll";
  1715. # InternalTimer( gettimeofday() + 1800, "netatmo_poll", $hash);
  1716. # return undef;
  1717. # }
  1718. $hash->{INTERVAL} = 3600 if(!defined($hash->{INTERVAL}));
  1719. if(defined($hash->{status}) && ($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/)) {
  1720. RemoveInternalTimer($hash);
  1721. InternalTimer(gettimeofday()+$hash->{INTERVAL}+1800, "netatmo_poll", $hash);
  1722. Log3 $name, 1, "$name: API usage limit reached";
  1723. $hash->{status} = "postponed update";
  1724. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1725. return undef;
  1726. }
  1727. $hash->{status} = "ok";
  1728. if( $hash->{SUBTYPE} eq "ACCOUNT" && defined($hash->{network}) && $hash->{network} eq "timeout" ) {
  1729. RemoveInternalTimer($hash);
  1730. InternalTimer(gettimeofday()+300, "netatmo_poll", $hash);
  1731. $hash->{status} = "recovering timeout";
  1732. netatmo_checkConnection($hash);
  1733. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1734. return undef;
  1735. } elsif( $hash->{SUBTYPE} eq "ACCOUNT" && defined($hash->{network}) && $hash->{network} ne "ok" ) {
  1736. RemoveInternalTimer($hash);
  1737. InternalTimer(gettimeofday()+600, "netatmo_poll", $hash);
  1738. $hash->{status} = "recovering network";
  1739. netatmo_checkConnection($hash);
  1740. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1741. Log3 $name, 5, "$name: ACCOUNT network error: ".$hash->{network};
  1742. return undef;
  1743. } elsif( $hash->{SUBTYPE} ne "ACCOUNT" && defined($hash->{IODev}->{network}) && $hash->{IODev}->{network} ne "ok" ) {
  1744. RemoveInternalTimer($hash);
  1745. InternalTimer(gettimeofday()+150, "netatmo_poll", $hash);
  1746. $hash->{status} = "delayed update";
  1747. #netatmo_checkConnection($hash->{IODev});
  1748. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1749. Log3 $name, 5, "$name: DEVICE network error: ".$hash->{IODev}->{network};
  1750. return undef;
  1751. }
  1752. Log3 $name, 3, "$name: poll ($hash->{SUBTYPE})";
  1753. if( $hash->{SUBTYPE} eq "ACCOUNT" ) {
  1754. netatmo_pollGlobal($hash);
  1755. netatmo_pollGlobalHealth($hash);
  1756. } elsif( $hash->{SUBTYPE} eq "DEVICE" ) {
  1757. netatmo_pollDevice($hash);
  1758. } elsif( $hash->{SUBTYPE} eq "MODULE" ) {
  1759. netatmo_pollDevice($hash);
  1760. } elsif( $hash->{SUBTYPE} eq "PUBLIC" ) {
  1761. netatmo_pollDevice($hash);
  1762. } elsif( $hash->{SUBTYPE} eq "FORECAST" ) {
  1763. netatmo_pollForecast($hash);
  1764. } elsif( $hash->{SUBTYPE} eq "HOME" ) {
  1765. netatmo_pollHome($hash);
  1766. } elsif( $hash->{SUBTYPE} eq "CAMERA" ) {
  1767. netatmo_pingCamera($hash);
  1768. } elsif( $hash->{SUBTYPE} eq "RELAY" ) {
  1769. netatmo_pollRelay($hash);
  1770. } elsif( $hash->{SUBTYPE} eq "THERMOSTAT" ) {
  1771. netatmo_pollThermostat($hash);
  1772. } elsif( $hash->{SUBTYPE} eq "PERSON" ) {
  1773. netatmo_pollPerson($hash);
  1774. } else {
  1775. Log3 $name, 1, "$name: unknown netatmo type $hash->{SUBTYPE} on poll";
  1776. return undef;
  1777. }
  1778. if( defined($hash->{helper}{update_count}) && $hash->{helper}{update_count} > 1024 ) {
  1779. InternalTimer(gettimeofday()+30, "netatmo_poll", $hash);
  1780. } else {
  1781. $hash->{helper}{NEXT_POLL} = int(gettimeofday())+$hash->{INTERVAL};
  1782. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "netatmo_poll", $hash);
  1783. }
  1784. }
  1785. sub
  1786. netatmo_dispatch($$$)
  1787. {
  1788. my ($param, $err, $data) = @_;
  1789. my $hash = $param->{hash};
  1790. my $name = $hash->{NAME};
  1791. if(!defined($param->{hash})){
  1792. Log3 "netatmo", 2, "netatmo: ".$param->{type}."dispatch fail (hash missing)";
  1793. return undef;
  1794. }
  1795. if(!defined($hash->{NAME})){
  1796. Log3 "netatmo", 2, "netatmo: ".$param->{type}."dispatch fail (name missing)";
  1797. return undef;
  1798. }
  1799. Log3 $name, 4, "$name: dispatch ($param->{type})";
  1800. $hash->{openRequests} -= 1 if( $param->{type} eq 'getmeasure' );
  1801. if( $err ) {
  1802. Log3 $name, 2, "$name: http request failed: $err";
  1803. if($err =~ /refused/ ){
  1804. RemoveInternalTimer($hash);
  1805. InternalTimer(gettimeofday()+3600, "netatmo_poll", $hash);
  1806. Log3 $name, 1, "$name: Possible IP Ban by Netatmo servers, try to change your IP and increase your request interval";
  1807. $hash->{status} = "banned";
  1808. $hash->{network} = "banned" if($hash->{SUBTYPE} eq "ACCOUNT");
  1809. }
  1810. elsif($err =~ /Invalid access token/){
  1811. RemoveInternalTimer($hash);
  1812. InternalTimer(gettimeofday()+300, "netatmo_poll", $hash);
  1813. $hash->{status} = "token";
  1814. $hash->{expires_at} = int(gettimeofday()) if($hash->{SUBTYPE} eq "ACCOUNT");
  1815. $hash->{IODev}->{expires_at} = int(gettimeofday()) if($hash->{SUBTYPE} ne "ACCOUNT");
  1816. }
  1817. elsif($err =~ /Bad hostname/ || $err =~ /gethostbyname/){
  1818. RemoveInternalTimer($hash);
  1819. InternalTimer(gettimeofday()+600, "netatmo_poll", $hash);
  1820. $hash->{status} = "timeout";
  1821. $hash->{network} = "dns" if($hash->{SUBTYPE} eq "ACCOUNT");
  1822. }
  1823. elsif($err =~ /timed out/){
  1824. RemoveInternalTimer($hash);
  1825. InternalTimer(gettimeofday()+300, "netatmo_poll", $hash);
  1826. $hash->{status} = "timeout";
  1827. $hash->{network} = "timeout" if($hash->{SUBTYPE} eq "ACCOUNT");
  1828. }
  1829. elsif($err =~ /Can't connect/){
  1830. RemoveInternalTimer($hash);
  1831. InternalTimer(gettimeofday()+300, "netatmo_poll", $hash);
  1832. $hash->{status} = "timeout";
  1833. $hash->{network} = "disconnected" if($hash->{SUBTYPE} eq "ACCOUNT");
  1834. #CommandDeleteReading( undef, "$hash->{NAME} vpn_url" ) if($hash->{SUBTYPE} eq "CAMERA");
  1835. }
  1836. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1837. return undef;
  1838. } elsif( $data ) {
  1839. $data =~ s/\n//g;
  1840. if( $data !~ m/^{.*}$/ ) {
  1841. RemoveInternalTimer($hash);
  1842. InternalTimer(gettimeofday()+300, "netatmo_poll", $hash);
  1843. Log3 $name, 2, "$name: invalid json detected";
  1844. Log3 $name, 5, "$name: $data";
  1845. $hash->{status} = "error";
  1846. $hash->{network} = "ok" if($hash->{SUBTYPE} eq "ACCOUNT");
  1847. $hash->{IODev}->{network} = "ok" if($hash->{SUBTYPE} ne "ACCOUNT");
  1848. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1849. return undef;
  1850. }
  1851. $hash->{network} = "ok" if($hash->{SUBTYPE} eq "ACCOUNT");
  1852. $hash->{IODev}->{network} = "ok" if($hash->{SUBTYPE} ne "ACCOUNT");
  1853. my $json = eval { JSON->new->utf8(0)->decode($data) };
  1854. if($@)
  1855. {
  1856. Log3 $name, 2, "$name: invalid json evaluation on dispatch type ".$param->{type}." ".$@;
  1857. return undef;
  1858. }
  1859. Log3 "unknown", 2, "unknown (no name) ".Dumper($hash) if(!defined($name));
  1860. Log3 $name, 4, "$name: dispatch return: ".$param->{type};
  1861. Log3 $name, 5, Dumper($json);
  1862. if( $json->{error} ) {
  1863. if(ref($json->{error}) ne "HASH") {
  1864. $hash->{STATE} = "LOGIN FAILED" if($hash->{SUBTYPE} eq "ACCOUNT");
  1865. $hash->{status} = $json->{error};
  1866. Log3 $name, 2, "$name: json message error: ".$json->{error};
  1867. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1868. return undef;
  1869. }
  1870. $hash->{status} = $json->{error}{message} if(defined($json->{error}{message}));
  1871. InternalTimer(gettimeofday()+1800, "netatmo_poll", $hash, 0) if($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/);
  1872. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  1873. return undef if($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/);
  1874. }
  1875. if( $param->{type} eq 'token' ) {
  1876. netatmo_parseToken($hash,$json);
  1877. } elsif( $param->{type} eq 'apptoken' ) {
  1878. netatmo_parseAppToken($hash,$json);
  1879. } elsif( $param->{type} eq 'devicelist' ) {
  1880. netatmo_parseDeviceList($hash,$json);
  1881. } elsif( $param->{type} eq 'stationsdata' ) {
  1882. netatmo_parseGlobal($hash,$json);
  1883. } elsif( $param->{type} eq 'forecastdata' ) {
  1884. netatmo_parseForecast($hash,$json);
  1885. } elsif( $param->{type} eq 'getmeasure' ) {
  1886. netatmo_parseReadings($hash,$json,$param->{requested});
  1887. } elsif( $param->{type} eq 'homelist' ) {
  1888. netatmo_parseHomeList($hash,$json);
  1889. } elsif( $param->{type} eq 'gethomedata' ) {
  1890. netatmo_parseHomeReadings($hash,$json);
  1891. } elsif( $param->{type} eq 'cameraping' ) {
  1892. netatmo_parseCameraPing($hash,$json);
  1893. } elsif( $param->{type} eq 'camerastatus' ) {
  1894. netatmo_parseCameraStatus($hash,$json);
  1895. } elsif( $param->{type} eq 'cameraconfig' ) {
  1896. netatmo_parseCameraConfig($hash,$json);
  1897. } elsif( $param->{type} eq 'tagstatus' ) {
  1898. netatmo_parseTagStatus($hash,$json);
  1899. } elsif( $param->{type} eq 'cameravideo' ) {
  1900. netatmo_parseCameraVideo($hash,$json);
  1901. } elsif( $param->{type} =~ /setpersonsstatus_/ ) {
  1902. netatmo_parsePersonsStatus($hash,$json,$param->{type});
  1903. } elsif( $param->{type} eq 'homecoachlist' ) {
  1904. netatmo_parseHomecoachList($hash,$json);
  1905. } elsif( $param->{type} eq 'thermostatlist' ) {
  1906. netatmo_parseThermostatList($hash,$json);
  1907. } elsif( $param->{type} eq 'getthermostatsdata' ) {
  1908. netatmo_parseThermostatReadings($hash,$json);
  1909. } elsif( $param->{type} eq 'setthermostat' ) {
  1910. netatmo_parseThermostatStatus($hash,$json);
  1911. } elsif( $param->{type} eq 'getpersondata' ) {
  1912. netatmo_parsePersonReadings($hash,$json);
  1913. } elsif( $param->{type} eq 'publicdata' ) {
  1914. return netatmo_parsePublic($hash,$json);
  1915. } elsif( $param->{type} eq 'address' ) {
  1916. return netatmo_parseAddress($hash,$json);
  1917. } elsif( $param->{type} eq 'latlng' ) {
  1918. return netatmo_parseLatLng($hash,$json);
  1919. } elsif( $param->{type} eq 'addwebhook' ) {
  1920. return netatmo_webhookStatus($hash,$json,"added");
  1921. } elsif( $param->{type} eq 'dropwebhook' ) {
  1922. return netatmo_webhookStatus($hash,$json,"dropped");
  1923. } elsif( $param->{type} eq 'sethomesettings' ) {
  1924. return netatmo_refreshHomeSettings($hash);
  1925. } else {
  1926. Log3 $name, 1, "$name: unknown '$param->{type}' ".Dumper($json);
  1927. }
  1928. }
  1929. }
  1930. sub
  1931. netatmo_parsePersonsStatus($$$)
  1932. {
  1933. my ($hash, $json, $param) = @_;
  1934. my $name = $hash->{NAME};
  1935. Log3 $name, 5, "$name: parsePersonsStatus ($param)\n".Dumper($json);
  1936. return if(!defined($json->{status}) || $json->{status} ne "ok");
  1937. if($hash->{SUBTYPE} eq "PERSON")
  1938. {
  1939. if($param =~ /away/)
  1940. {
  1941. readingsSingleUpdate( $hash, "status", "away", 1 );
  1942. }
  1943. else{
  1944. readingsSingleUpdate( $hash, "status", "home", 1 );
  1945. }
  1946. }
  1947. elsif($hash->{SUBTYPE} eq "HOME")
  1948. {
  1949. readingsSingleUpdate( $hash, "event", "Everyone left", 1 );
  1950. }
  1951. }
  1952. sub
  1953. netatmo_autocreate($;$)
  1954. {
  1955. my($hash,$force) = @_;
  1956. my $name = $hash->{NAME};
  1957. if( !$hash->{helper}{devices} ) {
  1958. netatmo_getDevices($hash,1);
  1959. return undef if( !$force );
  1960. }
  1961. if( !$force ) {
  1962. foreach my $d (keys %defs) {
  1963. next if(!defined($defs{$d}));
  1964. next if($defs{$d}{TYPE} ne "autocreate");
  1965. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  1966. }
  1967. }
  1968. my $autocreated = 0;
  1969. my $devices = $hash->{helper}{devices};
  1970. foreach my $device (@{$devices}) {
  1971. if( defined($modules{$hash->{TYPE}}{defptr}{"D$device->{_id}"}) ) {
  1972. Log3 $name, 4, "$name: device '$device->{_id}' already defined";
  1973. next;
  1974. }
  1975. if( defined($modules{$hash->{TYPE}}{defptr}{"M$device->{_id}"}) ) {
  1976. Log3 $name, 4, "$name: module '$device->{_id}' already defined";
  1977. next;
  1978. }
  1979. if(AttrVal($name,"ignored_device_ids","") =~ /$device->{_id}/) {
  1980. Log3 $name, 4, "$name: '$device->{_id}' ignored for autocreate";
  1981. next;
  1982. }
  1983. my $id = $device->{_id};
  1984. my $devname = "netatmo_D". $id;
  1985. $devname =~ s/:/_/g;
  1986. my $define= "$devname netatmo $id";
  1987. if( $device->{main_device} ) {
  1988. $devname = "netatmo_M". $id;
  1989. $devname =~ s/:/_/g;
  1990. $define= "$devname netatmo MODULE $device->{main_device} $id";
  1991. }
  1992. Log3 $name, 3, "$name: create new device '$devname' for device '$id'";
  1993. my $cmdret= CommandDefine(undef,$define);
  1994. if($cmdret) {
  1995. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  1996. } else {
  1997. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{module_name})) if( defined($device->{module_name}) );
  1998. $cmdret= CommandAttr(undef,"$devname room netatmo");
  1999. $cmdret= CommandAttr(undef,"$devname IODev $name");
  2000. $cmdret= CommandAttr(undef,"$devname devStateIcon .*:no-icon");
  2001. $autocreated++;
  2002. }
  2003. }
  2004. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  2005. return "created $autocreated devices";
  2006. }
  2007. sub
  2008. netatmo_autocreatehome($;$)
  2009. {
  2010. my($hash,$force) = @_;
  2011. my $name = $hash->{NAME};
  2012. if( !$hash->{helper}{homes} ) {
  2013. return undef if( !$force );
  2014. netatmo_getHomes($hash,1);
  2015. }
  2016. if( !$force ) {
  2017. foreach my $d (keys %defs) {
  2018. next if(!defined($defs{$d}));
  2019. next if($defs{$d}{TYPE} ne "autocreate");
  2020. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  2021. }
  2022. }
  2023. my $autocreated = 0;
  2024. my $homes = $hash->{helper}{homes};
  2025. foreach my $home (@{$homes}) {
  2026. if( defined($modules{$hash->{TYPE}}{defptr}{"H$home->{id}"}) ) {
  2027. Log3 $name, 4, "$name: home '$home->{id}' already defined";
  2028. next;
  2029. }
  2030. if( defined($modules{$hash->{TYPE}}{defptr}{"P$home->{id}"}) ) {
  2031. Log3 $name, 4, "$name: person '$home->{id}' already defined";
  2032. next;
  2033. }
  2034. foreach my $module (@{$home->{modules}}) {
  2035. if( defined($modules{$hash->{TYPE}}{defptr}{"G$module->{id}"}) ) {
  2036. Log3 $name, 4, "$name: tag '$module->{id}' already defined";
  2037. next;
  2038. }
  2039. my $tagid = $module->{id};
  2040. my $tagdevname = "netatmo_G". $tagid;
  2041. $tagdevname =~ s/:/_/g;
  2042. my $tagdefine= "$tagdevname netatmo TAG $home->{id} $tagid";
  2043. Log3 $name, 3, "$name: create new tag '$tagdevname' for camera '$home->{id}'";
  2044. my $tagcmdret= CommandDefine(undef,$tagdefine);
  2045. if($tagcmdret) {
  2046. Log3 $name, 1, "$name: Autocreate: An error occurred while creating tag for id '$tagid': $tagcmdret";
  2047. } else {
  2048. $tagcmdret= CommandAttr(undef,"$tagdevname alias ".encode_utf8($module->{name})) if( defined($module->{name}) );
  2049. $tagcmdret= CommandAttr(undef,"$tagdevname devStateIcon .*:no-icon");
  2050. $tagcmdret= CommandAttr(undef,"$tagdevname room netatmo");
  2051. $tagcmdret= CommandAttr(undef,"$tagdevname stateFormat status");
  2052. $tagcmdret= CommandAttr(undef,"$tagdevname IODev $name");
  2053. $autocreated++;
  2054. }
  2055. }
  2056. if( defined($modules{$hash->{TYPE}}{defptr}{"C$home->{id}"}) ) {
  2057. Log3 $name, 4, "$name: camera '$home->{id}' already defined";
  2058. next;
  2059. }
  2060. if(AttrVal($name,"ignored_device_ids","") =~ /$home->{id}/) {
  2061. Log3 $name, 4, "$name: '$home->{id}' ignored for autocreate";
  2062. next;
  2063. }
  2064. my $id = $home->{id};
  2065. my $devname = "netatmo_H". $id;
  2066. $devname =~ s/-/_/g;
  2067. my $define= "$devname netatmo HOME $id";
  2068. if( $home->{sd_status} ) {
  2069. $devname = "netatmo_C". $id;
  2070. $devname =~ s/:/_/g;
  2071. $devname =~ s/-/_/g;
  2072. $define= "$devname netatmo CAMERA $home->{home} $id";
  2073. }
  2074. elsif( $home->{face} ) {
  2075. next if(!defined($home->{pseudo})); #ignore unassigned faces
  2076. Log3 $name, 5, "$name: create new home/person '$devname' for home '$home->{home}'".Dumper($home);
  2077. $devname = "netatmo_P". $id;
  2078. $devname =~ s/-/_/g;
  2079. $define= "$devname netatmo PERSON $home->{home} $id";
  2080. }
  2081. $home->{home} = "?" if(!defined($home->{home}));
  2082. Log3 $name, 3, "$name: create new home/person '$devname' for home '$home->{home}'";
  2083. my $cmdret= CommandDefine(undef,$define);
  2084. if($cmdret) {
  2085. Log3 $name, 1, "$name: Autocreate: An error occurred while creating home for id '$id': $cmdret";
  2086. } else {
  2087. $cmdret= CommandAttr(undef,"$devname alias Unknown") if( defined($home->{face}) );
  2088. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($home->{pseudo})) if( defined($home->{pseudo}) );
  2089. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($home->{name})) if( defined($home->{name}) );
  2090. $cmdret= CommandAttr(undef,"$devname devStateIcon .*:no-icon");
  2091. $cmdret= CommandAttr(undef,"$devname room netatmo");
  2092. $cmdret= CommandAttr(undef,"$devname stateFormat status");
  2093. $cmdret= CommandAttr(undef,"$devname IODev $name");
  2094. $autocreated++;
  2095. }
  2096. }
  2097. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  2098. return "created $autocreated devices";
  2099. }
  2100. sub
  2101. netatmo_autocreatethermostat($;$)
  2102. {
  2103. my($hash,$force) = @_;
  2104. my $name = $hash->{NAME};
  2105. if( !$hash->{helper}{thermostats} ) {
  2106. netatmo_getThermostats($hash,1);
  2107. return undef if( !$force );
  2108. }
  2109. if( !$force ) {
  2110. foreach my $d (keys %defs) {
  2111. next if(!defined($defs{$d}));
  2112. next if($defs{$d}{TYPE} ne "autocreate");
  2113. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  2114. }
  2115. }
  2116. my $autocreated = 0;
  2117. my $devices = $hash->{helper}{thermostats};
  2118. foreach my $device (@{$devices}) {
  2119. if( defined($modules{$hash->{TYPE}}{defptr}{"R$device->{_id}"}) ) {
  2120. Log3 $name, 4, "$name: relay '$device->{_id}' already defined";
  2121. next;
  2122. }
  2123. if( defined($modules{$hash->{TYPE}}{defptr}{"T$device->{_id}"}) ) {
  2124. Log3 $name, 4, "$name: thermostat '$device->{_id}' already defined";
  2125. next;
  2126. }
  2127. if(AttrVal($name,"ignored_device_ids","") =~ /$device->{_id}/) {
  2128. Log3 $name, 4, "$name: '$device->{_id}' ignored for autocreate";
  2129. next;
  2130. }
  2131. my $id = $device->{_id};
  2132. my $devname = "netatmo_R". $id;
  2133. $devname =~ s/:/_/g;
  2134. my $define= "$devname netatmo RELAY $id";
  2135. if( $device->{main_device} ) {
  2136. $devname = "netatmo_T". $id;
  2137. $devname =~ s/:/_/g;
  2138. $define= "$devname netatmo THERMOSTAT $device->{main_device} $id";
  2139. }
  2140. Log3 $name, 3, "$name: create new device '$devname' for device '$id'";
  2141. my $cmdret= CommandDefine(undef,$define);
  2142. if($cmdret) {
  2143. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  2144. } else {
  2145. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{station_name})) if( defined($device->{station_name}) );
  2146. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{module_name})) if( defined($device->{module_name}) );
  2147. $cmdret= CommandAttr(undef,"$devname room netatmo");
  2148. $cmdret= CommandAttr(undef,"$devname IODev $name");
  2149. $cmdret= CommandAttr(undef,"$devname devStateIcon .*:no-icon");
  2150. $cmdret= CommandAttr(undef,"$devname stateFormat setpoint|temperature");
  2151. $autocreated++;
  2152. }
  2153. }
  2154. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  2155. return "created $autocreated devices";
  2156. }
  2157. sub
  2158. netatmo_autocreatehomecoach($;$)
  2159. {
  2160. my($hash,$force) = @_;
  2161. my $name = $hash->{NAME};
  2162. if( !$hash->{helper}{homecoachs} ) {
  2163. netatmo_getHomecoachs($hash,1);
  2164. return undef if( !$force );
  2165. }
  2166. if( !$force ) {
  2167. foreach my $d (keys %defs) {
  2168. next if(!defined($defs{$d}));
  2169. next if($defs{$d}{TYPE} ne "autocreate");
  2170. return undef if(AttrVal($defs{$d}{NAME},"disable",undef));
  2171. }
  2172. }
  2173. my $autocreated = 0;
  2174. my $devices = $hash->{helper}{homecoachs};
  2175. foreach my $device (@{$devices}) {
  2176. if( defined($modules{$hash->{TYPE}}{defptr}{"D$device->{_id}"}) ) {
  2177. Log3 $name, 4, "$name: homecoach '$device->{_id}' already defined";
  2178. next;
  2179. }
  2180. if(AttrVal($name,"ignored_device_ids","") =~ /$device->{_id}/) {
  2181. Log3 $name, 4, "$name: '$device->{_id}' ignored for autocreate";
  2182. next;
  2183. }
  2184. my $id = $device->{_id};
  2185. my $devname = "netatmo_D". $id;
  2186. $devname =~ s/:/_/g;
  2187. my $define= "$devname netatmo $id";
  2188. Log3 $name, 3, "$name: create new device '$devname' for device '$id'";
  2189. my $cmdret= CommandDefine(undef,$define);
  2190. if($cmdret) {
  2191. Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for id '$id': $cmdret";
  2192. } else {
  2193. $cmdret= CommandAttr(undef,"$devname alias ".encode_utf8($device->{name})) if( defined($device->{name}) );
  2194. $cmdret= CommandAttr(undef,"$devname room netatmo");
  2195. $cmdret= CommandAttr(undef,"$devname IODev $name");
  2196. $cmdret= CommandAttr(undef,"$devname devStateIcon .*:no-icon");
  2197. $cmdret= CommandAttr(undef,"$devname stateFormat health_idx");
  2198. $autocreated++;
  2199. }
  2200. }
  2201. CommandSave(undef,undef) if( $autocreated && AttrVal( "autocreate", "autosave", 1 ) );
  2202. return "created $autocreated devices";
  2203. }
  2204. sub
  2205. netatmo_parseToken($$)
  2206. {
  2207. my($hash, $json) = @_;
  2208. my $name = $hash->{NAME};
  2209. my $had_token = $hash->{access_token};
  2210. $hash->{access_token} = $json->{access_token};
  2211. $hash->{refresh_token} = $json->{refresh_token};
  2212. if( $hash->{access_token} ) {
  2213. $hash->{STATE} = "Connected";
  2214. $hash->{network} = "ok";
  2215. $hash->{expires_at} = int(gettimeofday());
  2216. $hash->{expires_at} += int($json->{expires_in}*0.8);
  2217. netatmo_getDevices($hash) if( !$had_token );
  2218. InternalTimer($hash->{expires_at}, "netatmo_refreshTokenTimer", $hash);
  2219. } else {
  2220. $hash->{expires_at} = int(gettimeofday());
  2221. $hash->{STATE} = "Error" if( !$hash->{access_token} );
  2222. Log3 $name, 1, "$name: token error ".Dumper($json);
  2223. InternalTimer(gettimeofday()+600, "netatmo_refreshTokenTimer", $hash);
  2224. }
  2225. }
  2226. sub
  2227. netatmo_parseAppToken($$)
  2228. {
  2229. my($hash, $json) = @_;
  2230. my $name = $hash->{NAME};
  2231. $hash->{access_token_app} = $json->{access_token};
  2232. $hash->{refresh_token_app} = $json->{refresh_token};
  2233. if( $hash->{access_token_app} ) {
  2234. $hash->{expires_at_app} = int(gettimeofday());
  2235. $hash->{expires_at_app} += int($json->{expires_in}*0.8);
  2236. InternalTimer($hash->{expires_at_app}, "netatmo_refreshAppTokenTimer", $hash);
  2237. } else {
  2238. $hash->{expires_at_app} = int(gettimeofday());
  2239. $hash->{STATE} = "Error" if( !$hash->{access_token_app} );
  2240. Log3 $name, 1, "$name: app token error ".Dumper($json);
  2241. InternalTimer(gettimeofday()+600, "netatmo_refreshAppTokenTimer", $hash);
  2242. }
  2243. }
  2244. sub
  2245. netatmo_parseDeviceList($$)
  2246. {
  2247. my($hash, $json) = @_;
  2248. my $name = $hash->{NAME};
  2249. Log3 $name, 4, "$name: parsedevicelist ";
  2250. #my $do_autocreate = 1;
  2251. #$do_autocreate = 0 if( !defined($hash->{helper}{devices}) ); #autocreate
  2252. my @devices = ();
  2253. foreach my $device (@{$json->{body}{devices}}) {
  2254. push( @devices, $device );
  2255. foreach my $module (@{$device->{modules}}) {
  2256. $module->{main_device} = $device->{_id};
  2257. push( @devices, $module );
  2258. }
  2259. }
  2260. $hash->{helper}{devices} = \@devices;
  2261. #netatmo_autocreate($hash) if( $do_autocreate );
  2262. }
  2263. sub
  2264. netatmo_parseHomeList($$)
  2265. {
  2266. my($hash, $json) = @_;
  2267. my $name = $hash->{NAME};
  2268. #my $do_autocreate = 1;
  2269. #$do_autocreate = 0 if( !defined($hash->{helper}{homes}) ); #autocreate
  2270. Log3 $name, 5, "$name: parsehomelist";
  2271. my @homes = ();
  2272. foreach my $home (@{$json->{body}{homes}}) {
  2273. push( @homes, $home );
  2274. foreach my $camera (@{$home->{cameras}}) {
  2275. $camera->{home} = $home->{id};
  2276. push( @homes, $camera ) if(defined($camera->{status}));
  2277. }
  2278. foreach my $person (@{$home->{persons}}) {
  2279. $person->{home} = $home->{id};
  2280. push( @homes, $person ) if(defined($person->{face}));
  2281. }
  2282. }
  2283. $hash->{helper}{homes} = \@homes;
  2284. #netatmo_autocreatehome($hash) if( $do_autocreate );
  2285. }
  2286. sub
  2287. netatmo_parseThermostatList($$)
  2288. {
  2289. my($hash, $json) = @_;
  2290. my $name = $hash->{NAME};
  2291. Log3 $name, 4, "$name: parsethermostatlist ";
  2292. #my $do_autocreate = 1;
  2293. #$do_autocreate = 0 if( !defined($hash->{helper}{devices}) ); #autocreate
  2294. my @devices = ();
  2295. foreach my $device (@{$json->{body}{devices}}) {
  2296. push( @devices, $device );
  2297. foreach my $module (@{$device->{modules}}) {
  2298. $module->{main_device} = $device->{_id};
  2299. push( @devices, $module );
  2300. }
  2301. }
  2302. $hash->{helper}{thermostats} = \@devices;
  2303. #netatmo_autocreate($hash) if( $do_autocreate );
  2304. }
  2305. sub
  2306. netatmo_parseHomecoachList($$)
  2307. {
  2308. my($hash, $json) = @_;
  2309. my $name = $hash->{NAME};
  2310. Log3 $name, 4, "$name: parsehomecoachlist ";
  2311. #my $do_autocreate = 1;
  2312. #$do_autocreate = 0 if( !defined($hash->{helper}{devices}) ); #autocreate
  2313. my @devices = ();
  2314. foreach my $device (@{$json->{body}{devices}}) {
  2315. push( @devices, $device );
  2316. foreach my $module (@{$device->{modules}}) {
  2317. $module->{main_device} = $device->{_id};
  2318. push( @devices, $module );
  2319. }
  2320. }
  2321. $hash->{helper}{homecoachs} = \@devices;
  2322. #netatmo_autocreate($hash) if( $do_autocreate );
  2323. }
  2324. sub
  2325. netatmo_updateReadings($$)
  2326. {
  2327. my($hash, $readings) = @_;
  2328. my $name = $hash->{NAME};
  2329. my ($seconds) = gettimeofday();
  2330. my $latest = 0;
  2331. if( $readings && @{$readings} ) {
  2332. my $i = 0;
  2333. foreach my $reading (sort { $a->[0] <=> $b->[0] } @{$readings}) {
  2334. if(!defined($reading->[0]) || !defined($reading->[1]) || !defined($reading->[2]))
  2335. {
  2336. Log3 $name, 1, "$name: invalid readings set: ".Dumper($reading);
  2337. next;
  2338. }
  2339. readingsBeginUpdate($hash);
  2340. $hash->{".updateTimestamp"} = FmtDateTime($reading->[0]);
  2341. readingsBulkUpdate( $hash, $reading->[1], $reading->[2] );
  2342. $hash->{CHANGETIME}[0] = FmtDateTime($reading->[0]);
  2343. readingsEndUpdate($hash,1);
  2344. $latest = $reading->[0] if( $reading->[0] > $latest );
  2345. }
  2346. readingsSingleUpdate( $hash, ".lastupdate", $seconds, 0 );
  2347. Log3 $name, 4, "$name: updatereadings";
  2348. }
  2349. return ($seconds,$latest);
  2350. }
  2351. sub
  2352. netatmo_parseReadings($$;$)
  2353. {
  2354. my($hash, $json, $requested) = @_;
  2355. my $name = $hash->{NAME};
  2356. Log3 $name, 4, "$name: parsereadings ".$requested;
  2357. my $reading_names = $hash->{helper}{readingNames};
  2358. if( $requested ) {
  2359. my @readings = split( ',', $requested );
  2360. $reading_names = \@readings;
  2361. }
  2362. if( $json ) {
  2363. $hash->{status} = $json->{status};
  2364. $hash->{status} = $json->{error}{message} if( $json->{error} );
  2365. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  2366. my @r = ();
  2367. my $readings = \@r;
  2368. $readings = $hash->{readings} if( defined($hash->{readings}) );
  2369. my ($time,$step_time,$last_time) = 0;
  2370. if( $hash->{status} eq "ok" )
  2371. {
  2372. if(scalar(@{$json->{body}}) == 0)
  2373. {
  2374. $hash->{status} = "no data";
  2375. }
  2376. foreach my $values ( @{$json->{body}}) {
  2377. $time = $values->{beg_time};
  2378. $step_time = $values->{step_time};
  2379. foreach my $value (@{$values->{value}}) {
  2380. my $i = 0;
  2381. foreach my $reading (@{$value}) {
  2382. #my $rname = $hash->{helper}{readingNames}[$i++];
  2383. my $rname = lc($reading_names->[$i++]);
  2384. if( !defined($reading) )
  2385. {
  2386. next;
  2387. }
  2388. if(lc($requested) =~ /wind/ && ($rname eq "temperature" || $rname eq "humidity"))
  2389. {
  2390. Log3 $name, 4, "$name netatmo - wind sensor $rname reading: $reading ($time)";
  2391. next;# if($reading == 0);
  2392. }
  2393. if(($rname eq "noise" && $reading > 150) || ($rname eq "temperature" && $reading > 60) || ($rname eq "humidity" && $reading > 100) || ($rname eq "pressure" && $reading < 500))
  2394. {
  2395. Log3 $name, 1, "$name netatmo - invalid reading: $rname: ".Dumper($reading)." \n ".Dumper($reading_names);
  2396. next;
  2397. }
  2398. if($rname eq "health_idx"){
  2399. $reading = $health_index{$reading};
  2400. }
  2401. push(@{$readings}, [$time, $rname, $reading]);
  2402. }
  2403. $last_time = $time if(defined($time));
  2404. $time += $step_time if( $step_time );
  2405. }
  2406. $hash->{last_status_store} = FmtDateTime($last_time);
  2407. }
  2408. if( $hash->{openRequests} > 1 ) {
  2409. $hash->{readings} = $readings;
  2410. } else {
  2411. my ($seconds,undef) = netatmo_updateReadings( $hash, $readings );
  2412. $hash->{LAST_POLL} = FmtDateTime( $seconds );
  2413. delete $hash->{readings};
  2414. }
  2415. if(defined($last_time) && int($last_time) > 0 && defined($step_time)) {
  2416. my $nextdata = $last_time + 2*$step_time + 10 + int(rand(20));
  2417. if($hash->{SUBTYPE} eq "MODULE")
  2418. {
  2419. my $devicehash = $modules{$hash->{TYPE}}{defptr}{"D$hash->{Device}"};
  2420. if(defined($devicehash) && defined($devicehash->{helper}{NEXT_POLL}))
  2421. {
  2422. $nextdata = ($devicehash->{helper}{NEXT_POLL} + 10 + int(rand(20)) ) if($devicehash->{helper}{NEXT_POLL} >= gettimeofday()+150);
  2423. if($nextdata >= (gettimeofday()+155))
  2424. {
  2425. RemoveInternalTimer($hash, "netatmo_poll");
  2426. InternalTimer($nextdata, "netatmo_poll", $hash);
  2427. $hash->{helper}{NEXT_POLL} = $nextdata;
  2428. Log3 $name, 3, "$name: next dynamic update from device ($requested) at ".FmtDateTime($nextdata);
  2429. } else {
  2430. $nextdata += $step_time;
  2431. if($nextdata >= (gettimeofday()+155))
  2432. {
  2433. RemoveInternalTimer($hash, "netatmo_poll");
  2434. InternalTimer($nextdata, "netatmo_poll", $hash);
  2435. $hash->{helper}{NEXT_POLL} = $nextdata;
  2436. Log3 $name, 3, "$name: next extended dynamic update from device ($requested) at ".FmtDateTime($nextdata);
  2437. } else {
  2438. Log3 $name, 2, "$name: invalid time for dynamic update from device ($requested): ".FmtDateTime($nextdata);
  2439. }
  2440. }
  2441. }
  2442. }
  2443. elsif($nextdata >= (gettimeofday()+280))
  2444. {
  2445. RemoveInternalTimer($hash, "netatmo_poll");
  2446. InternalTimer($nextdata, "netatmo_poll", $hash);
  2447. $hash->{helper}{NEXT_POLL} = $nextdata;
  2448. Log3 $name, 3, "$name: next dynamic update ($requested) at ".FmtDateTime($nextdata);
  2449. } else {
  2450. $nextdata += $step_time;
  2451. if($nextdata >= (gettimeofday()+280))
  2452. {
  2453. RemoveInternalTimer($hash, "netatmo_poll");
  2454. InternalTimer($nextdata, "netatmo_poll", $hash);
  2455. $hash->{helper}{NEXT_POLL} = $nextdata;
  2456. Log3 $name, 3, "$name: next extended dynamic update ($requested) at ".FmtDateTime($nextdata);
  2457. } else {
  2458. Log3 $name, 2, "$name: invalid time for dynamic update ($requested): ".FmtDateTime($nextdata);
  2459. }
  2460. }
  2461. }
  2462. }
  2463. }
  2464. else
  2465. {
  2466. $hash->{status} = "error";
  2467. }
  2468. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  2469. }
  2470. sub
  2471. netatmo_parseGlobal($$)
  2472. {
  2473. my($hash, $json) = @_;
  2474. my $name = $hash->{NAME};
  2475. Log3 $name, 4, "$name: parseGlobal";
  2476. if( $json )
  2477. {
  2478. Log3 $name, 5, "$name: ".Dumper($json);
  2479. $hash->{status} = $json->{status};
  2480. $hash->{status} = $json->{error}{message} if( $json->{error} );
  2481. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  2482. my @r = ();
  2483. my $readings = \@r;
  2484. $readings = $hash->{readings} if( defined($hash->{readings}) );
  2485. if( $hash->{status} eq "ok" )
  2486. {
  2487. $hash->{STATE} = "Connected";
  2488. foreach my $devicedata ( @{$json->{body}{devices}})
  2489. {
  2490. #Log3 $name, 5, "$name: device " . "D$devicedata->{_id} " .Dumper($devicedata);
  2491. my $device = $modules{$hash->{TYPE}}{defptr}{"D$devicedata->{_id}"};
  2492. next if (!defined($device));
  2493. #Log3 $name, 4, "$name: device " . "D$devicedata->{_id} found";
  2494. if(defined($devicedata->{dashboard_data}{AbsolutePressure}) && $devicedata->{dashboard_data}{AbsolutePressure} ne $devicedata->{dashboard_data}{Pressure})
  2495. {
  2496. readingsBeginUpdate($device);
  2497. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2498. readingsBulkUpdate( $device, "pressure_abs", $devicedata->{dashboard_data}{AbsolutePressure}, 1 );
  2499. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2500. readingsEndUpdate($device,1);
  2501. }
  2502. if(defined($devicedata->{dashboard_data}{pressure_trend}))
  2503. {
  2504. readingsBeginUpdate($device);
  2505. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2506. readingsBulkUpdate( $device, "pressure_trend", $devicedata->{dashboard_data}{pressure_trend}, 1 );
  2507. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2508. readingsEndUpdate($device,1);
  2509. }
  2510. if(defined($devicedata->{dashboard_data}{temp_trend}))
  2511. {
  2512. readingsBeginUpdate($device);
  2513. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2514. readingsBulkUpdate( $device, "temp_trend", $devicedata->{dashboard_data}{temp_trend}, 1 );
  2515. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2516. readingsEndUpdate($device,1);
  2517. }
  2518. if(defined($devicedata->{dashboard_data}{max_temp}) && $devicedata->{type} ne "NAModule2")
  2519. {
  2520. readingsBeginUpdate($device);
  2521. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{date_max_temp});
  2522. readingsBulkUpdate( $device, "temp_max", $devicedata->{dashboard_data}{max_temp}, 1 );
  2523. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{date_max_temp});
  2524. readingsEndUpdate($device,1);
  2525. }
  2526. if(defined($devicedata->{dashboard_data}{min_temp}) && $devicedata->{type} ne "NAModule2")
  2527. {
  2528. readingsBeginUpdate($device);
  2529. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{date_min_temp});
  2530. readingsBulkUpdate( $device, "temp_min", $devicedata->{dashboard_data}{min_temp}, 1 );
  2531. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{date_min_temp});
  2532. readingsEndUpdate($device,1);
  2533. }
  2534. if(defined($devicedata->{dashboard_data}{sum_rain_1}))
  2535. {
  2536. readingsBeginUpdate($device);
  2537. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2538. readingsBulkUpdate( $device, "rain_hour", $devicedata->{dashboard_data}{sum_rain_1}, 1 );
  2539. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2540. readingsEndUpdate($device,1);
  2541. }
  2542. if(defined($devicedata->{dashboard_data}{sum_rain_24}))
  2543. {
  2544. readingsBeginUpdate($device);
  2545. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2546. readingsBulkUpdate( $device, "rain_day", $devicedata->{dashboard_data}{sum_rain_24}, 1 );
  2547. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{time_utc});
  2548. readingsEndUpdate($device,1);
  2549. }
  2550. if(defined($devicedata->{dashboard_data}{max_wind_str}))
  2551. {
  2552. readingsBeginUpdate($device);
  2553. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{date_max_wind_str});
  2554. readingsBulkUpdate( $device, "windstrength_max", $devicedata->{dashboard_data}{max_wind_str}, 1 );
  2555. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{date_max_wind_str});
  2556. readingsEndUpdate($device,1);
  2557. }
  2558. if(defined($devicedata->{dashboard_data}{max_wind_angle}))
  2559. {
  2560. readingsBeginUpdate($device);
  2561. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{date_max_wind_str});
  2562. readingsBulkUpdate( $device, "windangle_max", $devicedata->{dashboard_data}{max_wind_angle}, 1 );
  2563. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{date_max_wind_str});
  2564. readingsEndUpdate($device,1);
  2565. }
  2566. if(defined($devicedata->{dashboard_data}{health_idx}) && $devicedata->{type} ne "NHC")
  2567. {
  2568. readingsBeginUpdate($device);
  2569. $device->{".updateTimestamp"} = FmtDateTime($devicedata->{dashboard_data}{health_idx});
  2570. readingsBulkUpdate( $device, "health_idx", $devicedata->{dashboard_data}{health_idx}, 1 );
  2571. $device->{CHANGETIME}[0] = FmtDateTime($devicedata->{dashboard_data}{health_idx});
  2572. readingsEndUpdate($device,1);
  2573. }
  2574. $device->{co2_calibrating} = $devicedata->{co2_calibrating} if(defined($devicedata->{co2_calibrating}));
  2575. $device->{last_status_store} = FmtDateTime($devicedata->{last_status_store}) if(defined($devicedata->{last_status_store}));
  2576. $device->{last_message} = FmtDateTime($devicedata->{last_message}) if(defined($devicedata->{last_message}));
  2577. $device->{last_seen} = FmtDateTime($devicedata->{last_seen}) if(defined($devicedata->{last_seen}));
  2578. $device->{wifi_status} = $devicedata->{wifi_status} if(defined($devicedata->{wifi_status}));
  2579. $device->{rf_status} = $devicedata->{rf_status} if(defined($devicedata->{rf_status}));
  2580. #$device->{battery_percent} = $devicedata->{battery_percent} if(defined($devicedata->{battery_percent}));
  2581. $device->{battery_vp} = $devicedata->{battery_vp} if(defined($devicedata->{battery_vp}));
  2582. readingsSingleUpdate($device, "battery", ($devicedata->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($devicedata->{battery_percent}));
  2583. readingsSingleUpdate($device, "battery_percent", $devicedata->{battery_percent}, 1) if(defined($devicedata->{battery_percent}));
  2584. if(defined($devicedata->{modules}))
  2585. {
  2586. foreach my $moduledata ( @{$devicedata->{modules}})
  2587. {
  2588. #Log3 $name, 5, "$name: module "."M$moduledata->{_id} ".Dumper($moduledata);
  2589. my $module = $modules{$hash->{TYPE}}{defptr}{"M$moduledata->{_id}"};
  2590. next if (!defined($module));
  2591. if(defined($moduledata->{dashboard_data}{AbsolutePressure}))
  2592. {
  2593. readingsBeginUpdate($module);
  2594. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2595. readingsBulkUpdate( $module, "pressure_abs", $moduledata->{dashboard_data}{AbsolutePressure}, 1 );
  2596. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2597. readingsEndUpdate($module,1);
  2598. }
  2599. if(defined($moduledata->{dashboard_data}{pressure_trend}))
  2600. {
  2601. readingsBeginUpdate($module);
  2602. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2603. readingsBulkUpdate( $module, "pressure_trend", $moduledata->{dashboard_data}{pressure_trend}, 1 );
  2604. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2605. readingsEndUpdate($module,1);
  2606. }
  2607. if(defined($moduledata->{dashboard_data}{temp_trend}))
  2608. {
  2609. readingsBeginUpdate($module);
  2610. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2611. readingsBulkUpdate( $module, "temp_trend", $moduledata->{dashboard_data}{temp_trend}, 1 );
  2612. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2613. readingsEndUpdate($module,1);
  2614. }
  2615. if(defined($moduledata->{dashboard_data}{max_temp}) && $moduledata->{type} ne "NAModule2")
  2616. {
  2617. readingsBeginUpdate($module);
  2618. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{date_max_temp});
  2619. readingsBulkUpdate( $module, "temp_max", $moduledata->{dashboard_data}{max_temp}, 1 );
  2620. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{date_max_temp});
  2621. readingsEndUpdate($module,1);
  2622. }
  2623. if(defined($moduledata->{dashboard_data}{min_temp}) && $moduledata->{type} ne "NAModule2")
  2624. {
  2625. readingsBeginUpdate($module);
  2626. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{date_min_temp});
  2627. readingsBulkUpdate( $module, "temp_min", $moduledata->{dashboard_data}{min_temp}, 1 );
  2628. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{date_min_temp});
  2629. readingsEndUpdate($module,1);
  2630. }
  2631. if(defined($moduledata->{dashboard_data}{sum_rain_1}))
  2632. {
  2633. readingsBeginUpdate($module);
  2634. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2635. readingsBulkUpdate( $module, "rain_hour", $moduledata->{dashboard_data}{sum_rain_1}, 1 );
  2636. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2637. readingsEndUpdate($module,1);
  2638. }
  2639. if(defined($moduledata->{dashboard_data}{sum_rain_24}))
  2640. {
  2641. readingsBeginUpdate($module);
  2642. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2643. readingsBulkUpdate( $module, "rain_day", $moduledata->{dashboard_data}{sum_rain_24}, 1 );
  2644. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{time_utc});
  2645. readingsEndUpdate($module,1);
  2646. }
  2647. if(defined($moduledata->{dashboard_data}{max_wind_str}))
  2648. {
  2649. readingsBeginUpdate($module);
  2650. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{date_max_wind_str});
  2651. readingsBulkUpdate( $module, "windstrength_max", $moduledata->{dashboard_data}{max_wind_str}, 1 );
  2652. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{date_max_wind_str});
  2653. readingsEndUpdate($module,1);
  2654. }
  2655. if(defined($moduledata->{dashboard_data}{max_wind_angle}))
  2656. {
  2657. readingsBeginUpdate($module);
  2658. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{dashboard_data}{date_max_wind_str});
  2659. readingsBulkUpdate( $module, "windangle_max", $moduledata->{dashboard_data}{max_wind_angle}, 1 );
  2660. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{dashboard_data}{date_max_wind_str});
  2661. readingsEndUpdate($module,1);
  2662. }
  2663. $module->{co2_calibrating} = $moduledata->{co2_calibrating} if(defined($moduledata->{co2_calibrating}));
  2664. $module->{last_status_store} = FmtDateTime($moduledata->{last_status_store}) if(defined($moduledata->{last_status_store}));
  2665. $module->{last_message} = FmtDateTime($moduledata->{last_message}) if(defined($moduledata->{last_message}));
  2666. $module->{last_seen} = FmtDateTime($moduledata->{last_seen}) if(defined($moduledata->{last_seen}));
  2667. $module->{wifi_status} = $moduledata->{wifi_status} if(defined($moduledata->{wifi_status}));
  2668. $module->{rf_status} = $moduledata->{rf_status} if(defined($moduledata->{rf_status}));
  2669. #$module->{battery_percent} = $moduledata->{battery_percent} if(defined($moduledata->{battery_percent}));
  2670. $module->{battery_vp} = $moduledata->{battery_vp} if(defined($moduledata->{battery_vp}));
  2671. readingsSingleUpdate($module, "battery", ($moduledata->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($moduledata->{battery_percent}));
  2672. readingsSingleUpdate($module, "battery_percent", $moduledata->{battery_percent}, 1) if(defined($moduledata->{battery_percent}));
  2673. }#foreach module
  2674. }#defined modules
  2675. }#foreach devices
  2676. }#ok
  2677. }#json
  2678. else
  2679. {
  2680. $hash->{status} = "error";
  2681. }
  2682. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  2683. return undef;
  2684. }
  2685. sub
  2686. netatmo_parseForecast($$)
  2687. {
  2688. my($hash, $json) = @_;
  2689. my $name = $hash->{NAME};
  2690. Log3 $name, 4, "$name: parseForecast";
  2691. if( $json )
  2692. {
  2693. Log3 $name, 5, "$name: ".Dumper($json);
  2694. $hash->{status} = $json->{status};
  2695. $hash->{status} = $json->{error}{message} if( $json->{error} );
  2696. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  2697. if( $hash->{status} eq "ok" )
  2698. {
  2699. #$hash->{STATE} = "Connected";
  2700. my $datatime = time;
  2701. my $forecasttime = time;
  2702. $hash->{stationname} = encode_utf8($json->{body}{stationname}) if(defined($json->{body}{stationname}));
  2703. $hash->{city} = encode_utf8($json->{body}{cityname}) if(defined($json->{body}{cityname}));
  2704. if(defined($json->{body}{current_temp_time}))
  2705. {
  2706. $hash->{time_data} = FmtDateTime($json->{body}{current_temp_time});
  2707. $datatime = $json->{body}{current_temp_time};
  2708. }
  2709. if(defined($json->{body}{time_current_symbol}))
  2710. {
  2711. $hash->{time_forecast} = FmtDateTime($json->{body}{time_current_symbol});
  2712. $forecasttime = $json->{body}{time_current_symbol};
  2713. }
  2714. return undef if($datatime <= $lastupdate);
  2715. readingsSingleUpdate($hash, ".lastupdate", $datatime, 0);
  2716. if($json->{body}{airqdata})
  2717. {
  2718. if(defined($json->{body}{airqdata}{data}))
  2719. {
  2720. #CommandDeleteReading( undef, "$hash->{NAME} air_.*" );
  2721. foreach my $airdata ( @{$json->{body}{airqdata}{data}})
  2722. {
  2723. my $timestamp = $airdata->{beg_time};
  2724. foreach my $airvalue ( @{$airdata->{value}})
  2725. {
  2726. readingsBeginUpdate($hash);
  2727. $hash->{".updateTimestamp"} = FmtDateTime($timestamp);
  2728. readingsBulkUpdate( $hash, "air_".@{$airvalue}[1], @{$airvalue}[0], 1 );
  2729. $hash->{CHANGETIME}[0] = FmtDateTime($timestamp);
  2730. readingsEndUpdate($hash,1);
  2731. next if(!defined(@{$airvalue}[2]));
  2732. readingsBeginUpdate($hash);
  2733. $hash->{".updateTimestamp"} = FmtDateTime($timestamp);
  2734. readingsBulkUpdate( $hash, "air_".@{$airvalue}[1]."_message", @{$airvalue}[2], 1 );
  2735. $hash->{CHANGETIME}[0] = FmtDateTime($timestamp);
  2736. readingsEndUpdate($hash,1);
  2737. }
  2738. }
  2739. }
  2740. }#airqdata
  2741. if(defined($json->{body}{current_windgust}))
  2742. {
  2743. readingsBeginUpdate($hash);
  2744. $hash->{".updateTimestamp"} = FmtDateTime($datatime);
  2745. readingsBulkUpdate( $hash, "windgust", $json->{body}{current_windgust}, 1 );
  2746. $hash->{CHANGETIME}[0] = FmtDateTime($datatime);
  2747. readingsEndUpdate($hash,1);
  2748. }
  2749. if(defined($json->{body}{current_windstrength}))
  2750. {
  2751. readingsBeginUpdate($hash);
  2752. $hash->{".updateTimestamp"} = FmtDateTime($datatime);
  2753. readingsBulkUpdate( $hash, "windstrength", $json->{body}{current_windstrength}, 1 );
  2754. $hash->{CHANGETIME}[0] = FmtDateTime($datatime);
  2755. readingsEndUpdate($hash,1);
  2756. }
  2757. if(defined($json->{body}{current_temp}))
  2758. {
  2759. readingsBeginUpdate($hash);
  2760. $hash->{".updateTimestamp"} = FmtDateTime($datatime);
  2761. readingsBulkUpdate( $hash, "temperature", $json->{body}{current_temp}, 1 );
  2762. $hash->{CHANGETIME}[0] = FmtDateTime($datatime);
  2763. readingsEndUpdate($hash,1);
  2764. }
  2765. if(defined($json->{body}{current_symbol}))
  2766. {
  2767. readingsBeginUpdate($hash);
  2768. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2769. readingsBulkUpdate( $hash, "symbol", $json->{body}{current_symbol}, 1 );
  2770. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2771. readingsEndUpdate($hash,1);
  2772. }
  2773. if(defined($json->{body}{forecastDays}))
  2774. {
  2775. my $i = 0;
  2776. foreach my $forecastdata ( @{$json->{body}{forecastDays}})
  2777. {
  2778. next if(ref($forecastdata) ne "HASH");
  2779. if(defined($forecastdata->{rain}))
  2780. {
  2781. readingsBeginUpdate($hash);
  2782. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2783. readingsBulkUpdate( $hash, "fc".$i."_rain", $forecastdata->{rain}, 1 );
  2784. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2785. readingsEndUpdate($hash,1);
  2786. }
  2787. if(defined($forecastdata->{max_temp}))
  2788. {
  2789. readingsBeginUpdate($hash);
  2790. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2791. readingsBulkUpdate( $hash, "fc".$i."_temp_max", $forecastdata->{max_temp}, 1 );
  2792. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2793. readingsEndUpdate($hash,1);
  2794. }
  2795. if(defined($forecastdata->{min_temp}))
  2796. {
  2797. readingsBeginUpdate($hash);
  2798. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2799. readingsBulkUpdate( $hash, "fc".$i."_temp_min", $forecastdata->{min_temp}, 1 );
  2800. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2801. readingsEndUpdate($hash,1);
  2802. }
  2803. if(defined($forecastdata->{windangle}))
  2804. {
  2805. readingsBeginUpdate($hash);
  2806. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2807. readingsBulkUpdate( $hash, "fc".$i."_windangle", $forecastdata->{windangle}, 1 );
  2808. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2809. readingsEndUpdate($hash,1);
  2810. }
  2811. if(defined($forecastdata->{wind_direction}))
  2812. {
  2813. readingsBeginUpdate($hash);
  2814. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2815. readingsBulkUpdate( $hash, "fc".$i."_wind_direction", $forecastdata->{wind_direction}, 1 );
  2816. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2817. readingsEndUpdate($hash,1);
  2818. }
  2819. if(defined($forecastdata->{windgust}))
  2820. {
  2821. readingsBeginUpdate($hash);
  2822. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2823. readingsBulkUpdate( $hash, "fc".$i."_windgust", $forecastdata->{windgust}, 1 );
  2824. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2825. readingsEndUpdate($hash,1);
  2826. }
  2827. if(defined($forecastdata->{sun}))
  2828. {
  2829. readingsBeginUpdate($hash);
  2830. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2831. readingsBulkUpdate( $hash, "fc".$i."_sun", $forecastdata->{sun}, 1 );
  2832. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2833. readingsEndUpdate($hash,1);
  2834. }
  2835. if(defined($forecastdata->{uv}))
  2836. {
  2837. readingsBeginUpdate($hash);
  2838. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2839. readingsBulkUpdate( $hash, "fc".$i."_uv", $forecastdata->{uv}, 1 );
  2840. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2841. readingsEndUpdate($hash,1);
  2842. }
  2843. if(defined($forecastdata->{sunset}))
  2844. {
  2845. readingsBeginUpdate($hash);
  2846. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2847. readingsBulkUpdate( $hash, "fc".$i."_sunset", FmtDateTime($forecastdata->{sunset}), 1 );
  2848. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2849. readingsEndUpdate($hash,1);
  2850. }
  2851. if(defined($forecastdata->{sunrise}))
  2852. {
  2853. readingsBeginUpdate($hash);
  2854. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2855. readingsBulkUpdate( $hash, "fc".$i."_sunrise", FmtDateTime($forecastdata->{sunrise}), 1 );
  2856. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2857. readingsEndUpdate($hash,1);
  2858. }
  2859. if(defined($forecastdata->{day_locale}))
  2860. {
  2861. readingsBeginUpdate($hash);
  2862. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2863. readingsBulkUpdate( $hash, "fc".$i."_day", encode_utf8($forecastdata->{day_locale}), 1 );
  2864. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2865. readingsEndUpdate($hash,1);
  2866. }
  2867. if(defined($forecastdata->{weather_symbol_day}))
  2868. {
  2869. readingsBeginUpdate($hash);
  2870. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2871. readingsBulkUpdate( $hash, "fc".$i."_symbol_day", $forecastdata->{weather_symbol_day}, 1 );
  2872. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2873. readingsEndUpdate($hash,1);
  2874. }
  2875. if(defined($forecastdata->{weather_symbol_night}))
  2876. {
  2877. readingsBeginUpdate($hash);
  2878. $hash->{".updateTimestamp"} = FmtDateTime($forecasttime);
  2879. readingsBulkUpdate( $hash, "fc".$i."_symbol_night", $forecastdata->{weather_symbol_night}, 1 );
  2880. $hash->{CHANGETIME}[0] = FmtDateTime($forecasttime);
  2881. readingsEndUpdate($hash,1);
  2882. }
  2883. $i++;
  2884. }#foreach forecast
  2885. }#defined forecastdays
  2886. }#ok
  2887. }#json
  2888. else
  2889. {
  2890. $hash->{status} = "error";
  2891. }
  2892. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  2893. return undef;
  2894. }
  2895. sub
  2896. netatmo_parseHomeReadings($$;$)
  2897. {
  2898. my($hash, $json) = @_;
  2899. my $name = $hash->{NAME};
  2900. Log3 $name, 4, "$name: parseHomeReadings";
  2901. if( $json ) {
  2902. Log3 $name, 5, "$name: ".Dumper($json);
  2903. $hash->{status} = "ok";
  2904. $hash->{status} = $json->{error}{message} if( $json->{error} );
  2905. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  2906. my @r = ();
  2907. my $readings = \@r;
  2908. $readings = $hash->{readings} if( defined($hash->{readings}) );
  2909. if( $hash->{status} eq "ok" )
  2910. {
  2911. #$hash->{STATE} = "Connected";
  2912. return undef if(!defined($json->{body}{homes}));
  2913. foreach my $homedata ( @{$json->{body}{homes}})
  2914. {
  2915. next if($homedata->{id} ne $hash->{Home});
  2916. readingsSingleUpdate($hash, "name", encode_utf8($homedata->{name}), 1) if(defined($homedata->{name}));
  2917. readingsSingleUpdate($hash, "presence_record_humans", $homedata->{presence_record_humans}, 1) if(defined($homedata->{presence_record_humans}));
  2918. readingsSingleUpdate($hash, "presence_record_vehicles", $homedata->{presence_record_vehicles}, 1) if(defined($homedata->{presence_record_vehicles}));
  2919. readingsSingleUpdate($hash, "presence_record_animals", $homedata->{presence_record_animals}, 1) if(defined($homedata->{presence_record_animals}));
  2920. readingsSingleUpdate($hash, "presence_record_movements", $homedata->{presence_record_movements}, 1) if(defined($homedata->{presence_record_movements}));
  2921. readingsSingleUpdate($hash, "presence_record_alarms", $homedata->{presence_record_alarms}, 1) if(defined($homedata->{presence_record_alarms}));
  2922. readingsSingleUpdate($hash, "gone_after", sprintf("%02d",(int($homedata->{gone_after}/60)/60)).":".sprintf("%02d",(int($homedata->{gone_after}/60)%60)), 1) if(defined($homedata->{gone_after}));
  2923. readingsSingleUpdate($hash, "smart_notifs", ($homedata->{smart_notifs} eq "1")?"on":"off", 1) if(defined($homedata->{smart_notifs}));
  2924. readingsSingleUpdate($hash, "presence_enable_notify_from_to", $homedata->{presence_enable_notify_from_to}, 1) if(defined($homedata->{presence_enable_notify_from_to}));
  2925. readingsSingleUpdate($hash, "presence_notify_from", sprintf("%02d",(int($homedata->{presence_notify_from}/60)/60)).":".sprintf("%02d",(int($homedata->{presence_notify_from}/60)%60)), 1) if(defined($homedata->{presence_notify_from}));
  2926. readingsSingleUpdate($hash, "presence_notify_to", sprintf("%02d",(int($homedata->{presence_notify_to}/60)/60)).":".sprintf("%02d",(int($homedata->{presence_notify_to}/60)%60)), 1) if(defined($homedata->{presence_notify_to}));
  2927. readingsSingleUpdate($hash, "notify_unknowns", $homedata->{notify_unknowns}, 1) if(defined($homedata->{notify_unknowns}));
  2928. readingsSingleUpdate($hash, "notify_movements", $homedata->{notify_movements}, 1) if(defined($homedata->{notify_movements}));
  2929. readingsSingleUpdate($hash, "record_alarms", $homedata->{record_alarms}, 1) if(defined($homedata->{record_alarms}));
  2930. readingsSingleUpdate($hash, "record_movements", $homedata->{record_movements}, 1) if(defined($homedata->{record_movements}));
  2931. if( $homedata->{place} ) {
  2932. $hash->{country} = encode_utf8($homedata->{place}{country}) if(defined($homedata->{place}{country}));
  2933. $hash->{bssid} = $homedata->{place}{bssid} if(defined($homedata->{place}{bssid}));
  2934. $hash->{altitude} = $homedata->{place}{altitude} if(defined($homedata->{place}{altitude}));
  2935. $hash->{city} = encode_utf8($homedata->{place}{geoip_city}) if(defined($homedata->{place}{geoip_city}));
  2936. $hash->{city} = encode_utf8($homedata->{place}{city}) if(defined($homedata->{place}{city}));;
  2937. $hash->{location} = $homedata->{place}{location}[1] .",". $homedata->{place}{location}[0] if(defined($homedata->{place}{location}));
  2938. $hash->{timezone} = encode_utf8($homedata->{place}{timezone}) if(defined($homedata->{place}{timezone}));
  2939. }
  2940. if(defined($homedata->{persons}))
  2941. {
  2942. foreach my $persondata ( @{$homedata->{persons}})
  2943. {
  2944. my $person = $modules{$hash->{TYPE}}{defptr}{"P$persondata->{id}"};
  2945. next if (!defined($person));
  2946. readingsSingleUpdate($person, "pseudo", encode_utf8($persondata->{pseudo}), 1) if(defined($persondata->{pseudo}));
  2947. readingsSingleUpdate($person, "last_seen", FmtDateTime($persondata->{last_seen}), 1) if(defined($persondata->{last_seen}));
  2948. readingsSingleUpdate($person, "out_of_sight", $persondata->{out_of_sight}, 1) if(defined($persondata->{out_of_sight}));
  2949. readingsSingleUpdate($person, "status", (($persondata->{out_of_sight} eq "0") ? "home" : "away"), 1) if(defined($persondata->{out_of_sight}));
  2950. #$person->{STATE} = ($persondata->{out_of_sight} eq "0") ? "home" : "away";
  2951. readingsSingleUpdate($person, "face_id", $persondata->{face}{id}, 0) if(defined($persondata->{face}{id}));
  2952. readingsSingleUpdate($person, "face_key", $persondata->{face}{key}, 0) if(defined($persondata->{face}{key}));
  2953. readingsSingleUpdate($person, "face_version", $persondata->{face}{version}, 1) if(defined($persondata->{face}{version}));
  2954. }
  2955. }
  2956. if(defined($homedata->{cameras}))
  2957. {
  2958. foreach my $cameradata ( @{$homedata->{cameras}})
  2959. {
  2960. my $camera = $modules{$hash->{TYPE}}{defptr}{"C$cameradata->{id}"};
  2961. next if (!defined($camera));
  2962. readingsSingleUpdate($camera, "name", encode_utf8($cameradata->{name}), 1) if(defined($cameradata->{name}));
  2963. readingsSingleUpdate($camera, "status", $cameradata->{status}, 1) if(defined($cameradata->{status}));
  2964. #$camera->{STATE} = ($cameradata->{status} eq "on") ? "online" : "offline";
  2965. readingsSingleUpdate($camera, "sd_status", $cameradata->{sd_status}, 0) if(defined($cameradata->{sd_status}));
  2966. readingsSingleUpdate($camera, "alim_status", $cameradata->{alim_status}, 0) if(defined($cameradata->{alim_status}));
  2967. readingsSingleUpdate($camera, "is_local", $cameradata->{is_local}, 1) if(defined($cameradata->{is_local}));
  2968. readingsSingleUpdate($camera, "vpn_url", $cameradata->{vpn_url}, 1) if(defined($cameradata->{vpn_url}));
  2969. CommandDeleteReading( undef, "$camera->{NAME} vpn_url" ) if(!defined($cameradata->{vpn_url}));
  2970. CommandDeleteReading( undef, "$camera->{NAME} local_url" ) if(!defined($cameradata->{vpn_url}));
  2971. readingsSingleUpdate($camera, "light_mode", $cameradata->{light_mode_status}, 1) if(defined($cameradata->{light_mode_status}));
  2972. readingsSingleUpdate($camera, "timelapse_available", $cameradata->{timelapse_available}, 0) if(defined($cameradata->{timelapse_available}));
  2973. delete($camera->{pin}) if($cameradata->{status} eq "on");
  2974. $camera->{model} = $cameradata->{type} if(defined($cameradata->{type}));
  2975. $camera->{firmware} = $cameradata->{firmware} if(defined($cameradata->{firmware}));
  2976. foreach my $tagdata ( @{$cameradata->{modules}})
  2977. {
  2978. my $tag = $modules{$hash->{TYPE}}{defptr}{"G$tagdata->{id}"};
  2979. next if (!defined($tag));
  2980. readingsSingleUpdate($tag, "name", encode_utf8($tagdata->{name}), 1) if(defined($tagdata->{name}));
  2981. readingsSingleUpdate($tag, "status", $tagdata->{status}, 1) if(defined($tagdata->{status}));
  2982. readingsSingleUpdate($tag, "category", $tagdata->{category}, 1) if(defined($tagdata->{category}));
  2983. $tag->{model} = $tagdata->{type};
  2984. $tag->{last_activity} = FmtDateTime($tagdata->{last_activity}) if(defined($tagdata->{last_activity}));
  2985. $tag->{last_seen} = FmtDateTime($tagdata->{last_seen}) if(defined($tagdata->{last_seen}));
  2986. $tag->{rf} = $tagdata->{rf};
  2987. $tag->{notify_rule} = $tagdata->{notify_rule};
  2988. $tag->{notify_rule} = $tagdata->{notify_rule};
  2989. readingsSingleUpdate($tag, "battery", ($tagdata->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($tagdata->{battery_percent}));
  2990. readingsSingleUpdate($tag, "battery_percent", $tagdata->{battery_percent}, 1) if(defined($tagdata->{battery_percent}));
  2991. }
  2992. }
  2993. }
  2994. if(defined($homedata->{events}))
  2995. {
  2996. my @eventslist = @{$homedata->{events}};
  2997. my $eventdata;
  2998. while ($eventdata = pop( @eventslist ))
  2999. {
  3000. $eventdata->{time} = time() if(!defined($eventdata->{time}));
  3001. next if($eventdata->{time} <= $lastupdate);
  3002. readingsSingleUpdate($hash, ".lastupdate", $eventdata->{time}, 0);
  3003. Log3 $name, 4, "$name: new event: ".FmtDateTime($eventdata->{time});
  3004. if(defined($eventdata->{event_list}))
  3005. {
  3006. my @singleeventslist = @{$eventdata->{event_list}};
  3007. my $singleeventdata;
  3008. while ($singleeventdata = pop( @singleeventslist ))
  3009. {
  3010. if(defined($singleeventdata->{message}))
  3011. {
  3012. my $eventmessage = $singleeventdata->{message};
  3013. $eventmessage = "-" if(!defined($singleeventdata->{message}));
  3014. $eventmessage =~ s/<\/b>//g;
  3015. $eventmessage =~ s/<b>//g;
  3016. readingsBeginUpdate($hash);
  3017. $hash->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3018. readingsBulkUpdate( $hash, "event", encode_utf8($eventmessage), 1 );
  3019. $hash->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3020. readingsEndUpdate($hash,1);
  3021. }
  3022. if(defined($singleeventdata->{snapshot}{key}))
  3023. {
  3024. readingsBeginUpdate($hash);
  3025. $hash->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3026. readingsBulkUpdate( $hash, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$singleeventdata->{snapshot}{id}."&key=".$singleeventdata->{snapshot}{key}, 1 );
  3027. $hash->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3028. readingsEndUpdate($hash,1);
  3029. }
  3030. }
  3031. }
  3032. else
  3033. {
  3034. my $eventmessage = $eventdata->{message};
  3035. $eventmessage = "-" if(!defined($eventdata->{message}));
  3036. $eventmessage =~ s/<\/b>//g;
  3037. $eventmessage =~ s/<b>//g;
  3038. if(defined($eventdata->{message}))
  3039. {
  3040. readingsBeginUpdate($hash);
  3041. $hash->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3042. readingsBulkUpdate( $hash, "event", encode_utf8($eventmessage), 1 );
  3043. $hash->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3044. readingsEndUpdate($hash,1);
  3045. }
  3046. if(defined($eventdata->{snapshot}))
  3047. {
  3048. readingsBeginUpdate($hash);
  3049. $hash->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3050. readingsBulkUpdate( $hash, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$eventdata->{snapshot}{id}."&key=".$eventdata->{snapshot}{key}, 1 );
  3051. $hash->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3052. readingsEndUpdate($hash,1);
  3053. }
  3054. }
  3055. my $camera = $modules{$hash->{TYPE}}{defptr}{"C$eventdata->{camera_id}"};
  3056. my $tag = $modules{$hash->{TYPE}}{defptr}{"G$eventdata->{module_id}"} if(defined($eventdata->{module_id}));
  3057. my $person = $modules{$hash->{TYPE}}{defptr}{"P$eventdata->{person_id}"} if(defined($eventdata->{person_id}));
  3058. if (defined($camera))
  3059. {
  3060. my $lastupdate = ReadingsVal( $camera->{NAME}, ".lastupdate", 0 );
  3061. next if($eventdata->{time} <= $lastupdate);
  3062. readingsSingleUpdate($camera, ".lastupdate", $eventdata->{time}, 0);
  3063. if(defined($eventdata->{event_list}))
  3064. {
  3065. my @singleeventslist = @{$eventdata->{event_list}};
  3066. my $singleeventdata;
  3067. while ($singleeventdata = pop( @singleeventslist ))
  3068. {
  3069. if(defined($singleeventdata->{message}))
  3070. {
  3071. my $cameraname = ReadingsVal( $camera->{NAME}, "name", "Welcome" );
  3072. my $eventmessage = $singleeventdata->{message};
  3073. $eventmessage =~ s/<b>//g;
  3074. $eventmessage =~ s/<\/b>//g;
  3075. $eventmessage =~ s/$cameraname: //g;
  3076. $eventmessage =~ s/$cameraname /Camera /g;
  3077. readingsBeginUpdate($camera);
  3078. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3079. readingsBulkUpdate( $camera, "event", encode_utf8($eventmessage), 1 );
  3080. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3081. readingsEndUpdate($camera,1);
  3082. }
  3083. if(defined($singleeventdata->{time}))
  3084. {
  3085. readingsBeginUpdate($camera);
  3086. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3087. readingsBulkUpdate( $camera, "event_time", FmtDateTime($singleeventdata->{time}), 1 );
  3088. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3089. readingsEndUpdate($camera,1);
  3090. }
  3091. if(defined($singleeventdata->{type}))
  3092. {
  3093. readingsBeginUpdate($camera);
  3094. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3095. readingsBulkUpdate( $camera, "event_type", $singleeventdata->{type}, 1 );
  3096. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3097. readingsEndUpdate($camera,1);
  3098. }
  3099. if(defined($singleeventdata->{id}))
  3100. {
  3101. readingsBeginUpdate($camera);
  3102. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3103. readingsBulkUpdate( $camera, "event_id", $singleeventdata->{id}, 1 );
  3104. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3105. readingsEndUpdate($camera,1);
  3106. }
  3107. if(defined($singleeventdata->{snapshot}{filename}))
  3108. {
  3109. readingsBeginUpdate($camera);
  3110. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3111. readingsBulkUpdate( $camera, "filename", $singleeventdata->{snapshot}{filename}, 1 );
  3112. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3113. readingsEndUpdate($camera,1);
  3114. }
  3115. if(defined($singleeventdata->{snapshot}{key}))
  3116. {
  3117. readingsBeginUpdate($camera);
  3118. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3119. readingsBulkUpdate( $camera, "snapshot", $singleeventdata->{snapshot}{id}."|".$singleeventdata->{snapshot}{key}, 1 );
  3120. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3121. readingsEndUpdate($camera,1);
  3122. }
  3123. if(defined($singleeventdata->{snapshot}{key}))
  3124. {
  3125. readingsBeginUpdate($camera);
  3126. $camera->{".updateTimestamp"} = FmtDateTime($singleeventdata->{time});
  3127. readingsBulkUpdate( $camera, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$singleeventdata->{snapshot}{id}."&key=".$singleeventdata->{snapshot}{key}, 1 );
  3128. $camera->{CHANGETIME}[0] = FmtDateTime($singleeventdata->{time});
  3129. readingsEndUpdate($camera,1);
  3130. }
  3131. }
  3132. }
  3133. else
  3134. {
  3135. if(defined($eventdata->{message}))
  3136. {
  3137. my $cameraname = ReadingsVal( $camera->{NAME}, "name", "Welcome" );
  3138. my $eventmessage = $eventdata->{message};
  3139. $eventmessage =~ s/<b>//g;
  3140. $eventmessage =~ s/<\/b>//g;
  3141. $eventmessage =~ s/$cameraname: //g;
  3142. $eventmessage =~ s/$cameraname /Camera /g;
  3143. readingsBeginUpdate($camera);
  3144. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3145. readingsBulkUpdate( $camera, "event", encode_utf8($eventmessage), 1 );
  3146. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3147. readingsEndUpdate($camera,1);
  3148. }
  3149. if(defined($eventdata->{time}))
  3150. {
  3151. readingsBeginUpdate($camera);
  3152. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3153. readingsBulkUpdate( $camera, "event_time", FmtDateTime($eventdata->{time}), 1 );
  3154. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3155. readingsEndUpdate($camera,1);
  3156. }
  3157. if(defined($eventdata->{type}))
  3158. {
  3159. readingsBeginUpdate($camera);
  3160. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3161. readingsBulkUpdate( $camera, "event_type", $eventdata->{type}, 1 );
  3162. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3163. readingsEndUpdate($camera,1);
  3164. }
  3165. if(defined($eventdata->{id}))
  3166. {
  3167. readingsBeginUpdate($camera);
  3168. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3169. readingsBulkUpdate( $camera, "event_id", $eventdata->{id}, 1 );
  3170. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3171. readingsEndUpdate($camera,1);
  3172. }
  3173. if(defined($person))
  3174. {
  3175. readingsBeginUpdate($camera);
  3176. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3177. readingsBulkUpdate( $camera, "person_seen", ReadingsVal($person->{NAME},"pseudo","Unknown"), 1 );
  3178. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3179. readingsEndUpdate($camera,1);
  3180. }
  3181. if(defined($eventdata->{snapshot}))
  3182. {
  3183. readingsBeginUpdate($camera);
  3184. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3185. readingsBulkUpdate( $camera, "snapshot", $eventdata->{snapshot}{id}."|".$eventdata->{snapshot}{key}, 1 );
  3186. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3187. readingsEndUpdate($camera,1);
  3188. }
  3189. if(defined($eventdata->{snapshot}))
  3190. {
  3191. readingsBeginUpdate($camera);
  3192. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3193. readingsBulkUpdate( $camera, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$eventdata->{snapshot}{id}."&key=".$eventdata->{snapshot}{key}, 1 );
  3194. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3195. readingsEndUpdate($camera,1);
  3196. }
  3197. }
  3198. if(defined($eventdata->{video_status}))
  3199. {
  3200. readingsBeginUpdate($camera);
  3201. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3202. readingsBulkUpdate( $camera, "video_status", $eventdata->{video_status}, 1 );
  3203. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3204. readingsEndUpdate($camera,1);
  3205. }
  3206. if(defined($eventdata->{video_id}))
  3207. {
  3208. readingsBeginUpdate($camera);
  3209. $camera->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3210. readingsBulkUpdate( $camera, "video_id", $eventdata->{video_id}, 1 );
  3211. $camera->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3212. readingsEndUpdate($camera,1);
  3213. }
  3214. }
  3215. if (defined($tag))
  3216. {
  3217. my $lastupdate = ReadingsVal( $tag->{NAME}, ".lastupdate", 0 );
  3218. next if($eventdata->{time} <= $lastupdate);
  3219. readingsSingleUpdate($tag, ".lastupdate", $eventdata->{time}, 0);
  3220. if(defined($eventdata->{message}))
  3221. {
  3222. my $tagname = ReadingsVal( $tag->{NAME}, "name", "Tag" );
  3223. my $eventmessage = $eventdata->{message};
  3224. $eventmessage =~ s/<b>//g;
  3225. $eventmessage =~ s/<\/b>//g;
  3226. $eventmessage =~ s/ by $tagname//g;
  3227. $eventmessage =~ s/$tagname /Tag /g;
  3228. readingsBeginUpdate($tag);
  3229. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3230. readingsBulkUpdate( $tag, "event", encode_utf8($eventmessage), 1 );
  3231. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3232. readingsEndUpdate($tag,1);
  3233. }
  3234. if(defined($eventdata->{time}))
  3235. {
  3236. readingsBeginUpdate($tag);
  3237. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3238. readingsBulkUpdate( $tag, "event_time", FmtDateTime($eventdata->{time}), 1 );
  3239. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3240. readingsEndUpdate($tag,1);
  3241. }
  3242. if(defined($eventdata->{type}))
  3243. {
  3244. readingsBeginUpdate($tag);
  3245. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3246. readingsBulkUpdate( $tag, "event_type", $eventdata->{type}, 1 );
  3247. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3248. readingsEndUpdate($tag,1);
  3249. }
  3250. if(defined($eventdata->{id}))
  3251. {
  3252. readingsBeginUpdate($tag);
  3253. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3254. readingsBulkUpdate( $tag, "event_id", $eventdata->{id}, 1 );
  3255. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3256. readingsEndUpdate($tag,1);
  3257. }
  3258. if(defined($eventdata->{snapshot}))
  3259. {
  3260. readingsBeginUpdate($tag);
  3261. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3262. readingsBulkUpdate( $tag, "snapshot", $eventdata->{snapshot}{id}."|".$eventdata->{snapshot}{key}, 1 );
  3263. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3264. readingsEndUpdate($tag,1);
  3265. }
  3266. if(defined($eventdata->{video_status}))
  3267. {
  3268. readingsBeginUpdate($tag);
  3269. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3270. readingsBulkUpdate( $tag, "video_status", $eventdata->{video_status}, 1 );
  3271. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3272. readingsEndUpdate($tag,1);
  3273. }
  3274. if(defined($eventdata->{video_id}))
  3275. {
  3276. readingsBeginUpdate($tag);
  3277. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3278. readingsBulkUpdate( $tag, "video_id", $eventdata->{video_id}, 1 );
  3279. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3280. readingsEndUpdate($tag,1);
  3281. }
  3282. if(defined($eventdata->{snapshot}))
  3283. {
  3284. readingsBeginUpdate($tag);
  3285. $tag->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3286. readingsBulkUpdate( $tag, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$eventdata->{snapshot}{id}."&key=".$eventdata->{snapshot}{key}, 1 );
  3287. $tag->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3288. readingsEndUpdate($tag,1);
  3289. }
  3290. }
  3291. if (defined($person))
  3292. {
  3293. my $lastupdate = ReadingsVal( $person->{NAME}, ".lastupdate", 0 );
  3294. next if($eventdata->{time} <= $lastupdate);
  3295. readingsSingleUpdate($person, ".lastupdate", $eventdata->{time}, 0);
  3296. readingsSingleUpdate($person, "last_seen", FmtDateTime($eventdata->{time}), 1) if(defined($eventdata->{time}));
  3297. readingsSingleUpdate($person, "last_arrival", FmtDateTime($eventdata->{time}), 1) if(defined($eventdata->{time}) && defined($eventdata->{is_arrival}) && $eventdata->{is_arrival} eq "1");
  3298. if(defined($camera))
  3299. {
  3300. readingsBeginUpdate($person);
  3301. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3302. readingsBulkUpdate( $person, "camera", ReadingsVal($camera->{NAME},"name","Unknown"), 1 );
  3303. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3304. readingsEndUpdate($person,1);
  3305. }
  3306. if(defined($eventdata->{id}))
  3307. {
  3308. readingsBeginUpdate($person);
  3309. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3310. readingsBulkUpdate( $person, "event_id", $eventdata->{id}, 1 );
  3311. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3312. readingsEndUpdate($person,1);
  3313. }
  3314. if(defined($eventdata->{video_status}))
  3315. {
  3316. readingsBeginUpdate($person);
  3317. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3318. readingsBulkUpdate( $person, "video_status", $eventdata->{video_status}, 1 );
  3319. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3320. readingsEndUpdate($person,1);
  3321. }
  3322. if(defined($eventdata->{video_id}))
  3323. {
  3324. readingsBeginUpdate($person);
  3325. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3326. readingsBulkUpdate( $person, "video_id", $eventdata->{video_id}, 1 );
  3327. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3328. readingsEndUpdate($person,1);
  3329. }
  3330. if(defined($eventdata->{snapshot}))
  3331. {
  3332. readingsBeginUpdate($person);
  3333. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3334. readingsBulkUpdate( $person, "snapshot", $eventdata->{snapshot}{id}."|".$eventdata->{snapshot}{key}, 1 );
  3335. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3336. readingsEndUpdate($person,1);
  3337. }
  3338. if(defined($eventdata->{snapshot}))
  3339. {
  3340. readingsBeginUpdate($person);
  3341. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3342. readingsBulkUpdate( $person, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$eventdata->{snapshot}{id}."&key=".$eventdata->{snapshot}{key}, 1 );
  3343. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3344. readingsEndUpdate($person,1);
  3345. }
  3346. }
  3347. }
  3348. }
  3349. my $time = $homedata->{time_server};
  3350. }
  3351. }
  3352. }
  3353. else
  3354. {
  3355. $hash->{status} = "error";
  3356. }
  3357. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3358. }
  3359. sub
  3360. netatmo_refreshHomeSettings($)
  3361. {
  3362. my($hash) = @_;
  3363. my $name = $hash->{NAME};
  3364. InternalTimer(gettimeofday()+5, "netatmo_poll", $hash);
  3365. return undef;
  3366. }
  3367. sub
  3368. netatmo_parseCameraPing($$;$)
  3369. {
  3370. my($hash, $json) = @_;
  3371. my $name = $hash->{NAME};
  3372. Log3 $name, 4, "$name: parseCameraPing";
  3373. if( $json ) {
  3374. Log3 $name, 5, "$name: ".Dumper($json);
  3375. $hash->{status} = $json->{status};
  3376. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3377. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  3378. readingsSingleUpdate($hash, "local_url", $json->{local_url}, 1) if(defined($json->{local_url}));
  3379. CommandDeleteReading( undef, "$hash->{NAME} local_url" ) if(!defined($json->{local_url}));
  3380. }
  3381. else
  3382. {
  3383. $hash->{status} = "error";
  3384. if(ReadingsVal( $name, "status", "ok" ) eq "disconnected"){
  3385. $hash->{status} = "disconnected";
  3386. RemoveInternalTimer($hash);
  3387. }
  3388. }
  3389. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if(defined($hash->{status}) && $hash->{status} ne "no data");
  3390. }
  3391. sub
  3392. netatmo_parseCameraStatus($$;$)
  3393. {
  3394. my($hash, $json) = @_;
  3395. my $name = $hash->{NAME};
  3396. Log3 $name, 4, "$name: parseCameraStatus";
  3397. my $home = $modules{$hash->{TYPE}}{defptr}{"H$hash->{Home}"};
  3398. if( $json ) {
  3399. Log3 $name, 5, "$name: ".Dumper($json);
  3400. $hash->{status} = "ok";
  3401. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3402. InternalTimer( gettimeofday() + 10, "netatmo_pollHome", $home) if($hash->{status} eq "ok" );
  3403. }
  3404. else{
  3405. netatmo_pollHome($home) if($home->{status} !~ /usage/ && $home->{status} !~ /too_many_connections/ && $home->{status} !~ /postponed/);
  3406. }
  3407. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3408. }
  3409. sub
  3410. netatmo_parseCameraConfig($$;$)
  3411. {
  3412. my($hash, $json) = @_;
  3413. my $name = $hash->{NAME};
  3414. Log3 $name, 4, "$name: parseCameraConfig";
  3415. my $home = $modules{$hash->{TYPE}}{defptr}{"H$hash->{Home}"};
  3416. if( $json ) {
  3417. Log3 $name, 5, "$name: ".Dumper($json);
  3418. $hash->{status} = "ok";
  3419. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3420. return undef if($hash->{status} ne "ok");
  3421. readingsBeginUpdate($hash);
  3422. readingsBulkUpdate( $hash, "intensity", $json->{intensity}, 1 ) if( $json->{intensity} );
  3423. readingsBulkUpdate( $hash, "light_mode", $json->{mode}, 1 ) if( $json->{mode} );
  3424. readingsBulkUpdate( $hash, "night_always", ($json->{night}{always}?"true":"false"), 1 ) if( $json->{night} );
  3425. readingsBulkUpdate( $hash, "night_person", ($json->{night}{person}?"true":"false"), 1 ) if( $json->{night} );
  3426. readingsBulkUpdate( $hash, "night_vehicle", ($json->{night}{vehicle}?"true":"false"), 1 ) if( $json->{night} );
  3427. readingsBulkUpdate( $hash, "night_animal", ($json->{night}{animal}?"true":"false"), 1 ) if( $json->{night} );
  3428. readingsBulkUpdate( $hash, "night_movement", ($json->{night}{movement}?"true":"false"), 1 ) if( $json->{night} );
  3429. readingsEndUpdate( $hash, 1);
  3430. InternalTimer( gettimeofday() + 10, "netatmo_pollHome", $home) if($hash->{status} eq "ok" );
  3431. }
  3432. else{
  3433. netatmo_pollHome($home) if($home->{status} !~ /usage/ && $home->{status} !~ /too_many_connections/ && $home->{status} !~ /postponed/);
  3434. }
  3435. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3436. }
  3437. sub
  3438. netatmo_parseTagStatus($$;$)
  3439. {
  3440. my($hash, $json) = @_;
  3441. my $name = $hash->{NAME};
  3442. Log3 $name, 4, "$name: parseTagStatus";
  3443. if( $json ) {
  3444. Log3 $name, 5, "$name: ".Dumper($json);
  3445. $hash->{status} = $json->{status};
  3446. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3447. readingsSingleUpdate($hash, "status", "calibrating", 1) if($hash->{status} eq "ok");
  3448. }
  3449. else
  3450. {
  3451. $hash->{status} = "error";
  3452. }
  3453. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3454. }
  3455. sub
  3456. netatmo_parseCameraVideo($$;$)
  3457. {
  3458. my($hash, $json) = @_;
  3459. my $name = $hash->{NAME};
  3460. Log3 $name, 4, "$name: parseCameraVideo";
  3461. if( $json ) {
  3462. Log3 $name, 5, "$name: ".Dumper($json);
  3463. $hash->{status} = $json->{status};
  3464. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3465. return undef if($hash->{status} ne "ok");
  3466. return undef if($hash->{status} ne "ok");
  3467. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  3468. readingsSingleUpdate($hash, "local_url", $json->{local_url}, 1) if(defined($json->{local_url}));
  3469. }
  3470. else
  3471. {
  3472. $hash->{status} = "error";
  3473. }
  3474. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3475. }
  3476. sub
  3477. netatmo_parsePersonReadings($$;$)
  3478. {
  3479. my($hash, $json) = @_;
  3480. my $name = $hash->{NAME};
  3481. Log3 $name, 4, "$name: parsePersonReadings";
  3482. if( $json ) {
  3483. Log3 $name, 5, "$name: ".Dumper($json);
  3484. $hash->{status} = $json->{status};
  3485. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3486. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  3487. if( $hash->{status} eq "ok" )
  3488. {
  3489. #$hash->{STATE} = "Connected";
  3490. if(defined($json->{body}{events_list}))
  3491. {
  3492. my @eventslist = @{$json->{body}{events_list}};
  3493. my $eventdata;
  3494. while ($eventdata = pop( @eventslist ))
  3495. {
  3496. next if(!defined($eventdata->{person_id}));
  3497. next if($eventdata->{time} <= $lastupdate);
  3498. next if($eventdata->{person_id} ne $hash->{Person});
  3499. $eventdata->{time} = time() if(!defined($eventdata->{time}));
  3500. readingsSingleUpdate($hash, ".lastupdate", $eventdata->{time}, 0);
  3501. Log3 $name, 4, "$name: new event: ".FmtDateTime($eventdata->{time});
  3502. my $camera = $modules{$hash->{TYPE}}{defptr}{"C$eventdata->{camera_id}"};
  3503. my $person = $modules{$hash->{TYPE}}{defptr}{"P$eventdata->{person_id}"} if(defined($eventdata->{person_id}));
  3504. if (defined($person))
  3505. {
  3506. readingsSingleUpdate($person, "last_seen", FmtDateTime($eventdata->{time}), 1) if(defined($eventdata->{time}));
  3507. readingsSingleUpdate($person, "last_arrival", FmtDateTime($eventdata->{time}), 1) if(defined($eventdata->{time}) && defined($eventdata->{is_arrival}) && $eventdata->{is_arrival} eq "1");
  3508. if(defined($camera))
  3509. {
  3510. readingsBeginUpdate($person);
  3511. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3512. readingsBulkUpdate( $person, "camera", ReadingsVal($camera->{NAME},"name","Unknown"), 1 );
  3513. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3514. readingsEndUpdate($person,1);
  3515. }
  3516. if(defined($eventdata->{id}))
  3517. {
  3518. readingsBeginUpdate($person);
  3519. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3520. readingsBulkUpdate( $person, "event_id", $eventdata->{id}, 1 );
  3521. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3522. readingsEndUpdate($person,1);
  3523. }
  3524. if(defined($eventdata->{video_status}))
  3525. {
  3526. readingsBeginUpdate($person);
  3527. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3528. readingsBulkUpdate( $person, "video_status", $eventdata->{video_status}, 1 );
  3529. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3530. readingsEndUpdate($person,1);
  3531. }
  3532. if(defined($eventdata->{video_id}))
  3533. {
  3534. readingsBeginUpdate($person);
  3535. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3536. readingsBulkUpdate( $person, "video_id", $eventdata->{video_id}, 1 );
  3537. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3538. readingsEndUpdate($person,1);
  3539. }
  3540. if(defined($eventdata->{snapshot}))
  3541. {
  3542. readingsBeginUpdate($person);
  3543. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3544. readingsBulkUpdate( $person, "snapshot", $eventdata->{snapshot}{id}."|".$eventdata->{snapshot}{key}, 1 );
  3545. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3546. readingsEndUpdate($person,1);
  3547. }
  3548. if(defined($eventdata->{snapshot}))
  3549. {
  3550. readingsBeginUpdate($person);
  3551. $person->{".updateTimestamp"} = FmtDateTime($eventdata->{time});
  3552. readingsBulkUpdate( $person, "last_snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$eventdata->{snapshot}{id}."&key=".$eventdata->{snapshot}{key}, 1 );
  3553. $person->{CHANGETIME}[0] = FmtDateTime($eventdata->{time});
  3554. readingsEndUpdate($person,1);
  3555. }
  3556. }
  3557. }
  3558. }
  3559. }
  3560. }
  3561. else
  3562. {
  3563. $hash->{status} = "error";
  3564. }
  3565. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3566. }
  3567. sub
  3568. netatmo_parseThermostatReadings($$;$)
  3569. {
  3570. my($hash, $json) = @_;
  3571. my $name = $hash->{NAME};
  3572. Log3 $name, 4, "$name: parseThermostatReadings";
  3573. if( $json ) {
  3574. Log3 $name, 5, "$name: ".Dumper($json);
  3575. $hash->{status} = $json->{status};
  3576. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3577. my $lastupdate = ReadingsVal( $name, ".lastupdate", 0 );
  3578. my @r = ();
  3579. my $readings = \@r;
  3580. $readings = $hash->{readings} if( defined($hash->{readings}) );
  3581. if( $hash->{status} eq "ok" )
  3582. {
  3583. foreach my $devicedata ( @{$json->{body}{devices}})
  3584. {
  3585. my $hash = $modules{$hash->{TYPE}}{defptr}{"R$devicedata->{_id}"};
  3586. next if (!defined($hash));
  3587. next if($devicedata->{_id} ne $hash->{Relay});
  3588. #$hash->{STATE} = "Connected";
  3589. readingsSingleUpdate($hash, "name", encode_utf8($devicedata->{station_name}), 1) if(defined($devicedata->{station_name}));
  3590. $hash->{stationName} = encode_utf8($devicedata->{station_name}) if( $devicedata->{station_name} );
  3591. $hash->{moduleName} = encode_utf8($devicedata->{module_name}) if( $devicedata->{module_name} );
  3592. $hash->{model} = $devicedata->{type} if(defined($devicedata->{type}));
  3593. $hash->{firmware} = $devicedata->{firmware} if(defined($devicedata->{firmware}));
  3594. $hash->{last_upgrade} = FmtDateTime($devicedata->{last_upgrade}) if(defined($devicedata->{last_upgrade}));
  3595. $hash->{date_setup} = FmtDateTime($devicedata->{date_setup}) if(defined($devicedata->{date_setup}));
  3596. $hash->{last_setup} = FmtDateTime($devicedata->{last_setup}) if(defined($devicedata->{last_setup}));
  3597. $hash->{last_status_store} = FmtDateTime($devicedata->{last_status_store}) if(defined($devicedata->{last_status_store}));
  3598. $hash->{last_message} = FmtDateTime($devicedata->{last_message}) if(defined($devicedata->{last_message}));
  3599. $hash->{last_seen} = FmtDateTime($devicedata->{last_seen}) if(defined($devicedata->{last_seen}));
  3600. $hash->{last_plug_seen} = FmtDateTime($devicedata->{last_plug_seen}) if(defined($devicedata->{last_plug_seen}));
  3601. $hash->{last_therm_seen} = FmtDateTime($devicedata->{last_therm_seen}) if(defined($devicedata->{last_therm_seen}));
  3602. $hash->{wifi_status} = $devicedata->{wifi_status} if(defined($devicedata->{wifi_status}));
  3603. $hash->{rf_status} = $devicedata->{rf_status} if(defined($devicedata->{rf_status}));
  3604. #$hash->{battery_percent} = $devicedata->{battery_percent} if(defined($devicedata->{battery_percent}));
  3605. $hash->{battery_vp} = $devicedata->{battery_vp} if(defined($devicedata->{battery_vp}));
  3606. $hash->{therm_orientation} = $devicedata->{therm_orientation} if(defined($devicedata->{therm_orientation}));
  3607. $hash->{therm_relay_cmd} = $devicedata->{therm_relay_cmd} if(defined($devicedata->{therm_relay_cmd}));
  3608. $hash->{udp_conn} = $devicedata->{udp_conn} if(defined($devicedata->{udp_conn}));
  3609. $hash->{plug_connected_boiler} = $devicedata->{plug_connected_boiler} if(defined($devicedata->{plug_connected_boiler}));
  3610. $hash->{syncing} = $devicedata->{syncing} if(defined($devicedata->{syncing}));
  3611. if( $devicedata->{place} ) {
  3612. $hash->{country} = encode_utf8($devicedata->{place}{country});
  3613. $hash->{bssid} = $devicedata->{place}{bssid} if(defined($devicedata->{place}{bssid}));
  3614. $hash->{altitude} = $devicedata->{place}{altitude} if(defined($devicedata->{place}{altitude}));
  3615. $hash->{city} = encode_utf8($devicedata->{place}{geoip_city}) if(defined($devicedata->{place}{geoip_city}));
  3616. $hash->{city} = encode_utf8($devicedata->{place}{city}) if(defined($devicedata->{place}{city}));;
  3617. $hash->{location} = $devicedata->{place}{location}[1] .",". $devicedata->{place}{location}[0];
  3618. $hash->{timezone} = encode_utf8($devicedata->{place}{timezone});
  3619. }
  3620. readingsSingleUpdate($hash, "battery", ($devicedata->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($devicedata->{battery_percent}));
  3621. readingsSingleUpdate($hash, "battery_percent", $devicedata->{battery_percent}, 1) if(defined($devicedata->{battery_percent}));
  3622. if(defined($devicedata->{modules}))
  3623. {
  3624. foreach my $moduledata ( @{$devicedata->{modules}})
  3625. {
  3626. my $module = $modules{$hash->{TYPE}}{defptr}{"T$moduledata->{_id}"};
  3627. next if (!defined($module));
  3628. $module->{stationName} = encode_utf8($moduledata->{station_name}) if( $moduledata->{station_name} );
  3629. $module->{moduleName} = encode_utf8($moduledata->{module_name}) if( $moduledata->{module_name} );
  3630. $module->{model} = $moduledata->{type} if(defined($moduledata->{type}));
  3631. $module->{firmware} = $moduledata->{firmware} if(defined($moduledata->{firmware}));
  3632. $module->{last_upgrade} = FmtDateTime($moduledata->{last_upgrade}) if(defined($moduledata->{last_upgrade}));
  3633. $module->{date_setup} = FmtDateTime($moduledata->{date_setup}) if(defined($moduledata->{date_setup}));
  3634. $module->{last_setup} = FmtDateTime($moduledata->{last_setup}) if(defined($moduledata->{last_setup}));
  3635. $module->{last_status_store} = FmtDateTime($moduledata->{last_status_store}) if(defined($moduledata->{last_status_store}));
  3636. $module->{last_message} = FmtDateTime($moduledata->{last_message}) if(defined($moduledata->{last_message}));
  3637. $module->{last_seen} = FmtDateTime($moduledata->{last_seen}) if(defined($moduledata->{last_seen}));
  3638. $module->{last_plug_seen} = FmtDateTime($moduledata->{last_plug_seen}) if(defined($moduledata->{last_plug_seen}));
  3639. $module->{last_therm_seen} = FmtDateTime($moduledata->{last_therm_seen}) if(defined($moduledata->{last_therm_seen}));
  3640. $module->{wifi_status} = $moduledata->{wifi_status} if(defined($moduledata->{wifi_status}));
  3641. $module->{rf_status} = $moduledata->{rf_status} if(defined($moduledata->{rf_status}));
  3642. #$module->{battery_percent} = $moduledata->{battery_percent} if(defined($moduledata->{battery_percent}));
  3643. $module->{battery_vp} = $moduledata->{battery_vp} if(defined($moduledata->{battery_vp}));
  3644. $module->{therm_orientation} = $moduledata->{therm_orientation} if(defined($moduledata->{therm_orientation}));
  3645. #$module->{therm_relay_cmd} = $moduledata->{therm_relay_cmd} if(defined($moduledata->{therm_relay_cmd}));
  3646. $module->{udp_conn} = $moduledata->{udp_conn} if(defined($moduledata->{udp_conn}));
  3647. $module->{plug_connected_boiler} = $moduledata->{plug_connected_boiler} if(defined($moduledata->{plug_connected_boiler}));
  3648. $module->{syncing} = $moduledata->{syncing} if(defined($moduledata->{syncing}));
  3649. if( $moduledata->{place} ) {
  3650. $module->{country} = $moduledata->{place}{country};
  3651. $module->{bssid} = $moduledata->{place}{bssid} if(defined($moduledata->{place}{bssid}));
  3652. $module->{altitude} = $moduledata->{place}{altitude} if(defined($moduledata->{place}{altitude}));
  3653. $module->{city} = encode_utf8($moduledata->{place}{geoip_city}) if(defined($moduledata->{place}{geoip_city}));
  3654. $module->{city} = encode_utf8($moduledata->{place}{city}) if(defined($moduledata->{place}{city}));;
  3655. $module->{location} = $moduledata->{place}{location}[1] .",". $moduledata->{place}{location}[0];
  3656. $module->{timezone} = encode_utf8($moduledata->{place}{timezone});
  3657. }
  3658. readingsSingleUpdate($module, "battery", ($moduledata->{battery_percent} > 20) ? "ok" : "low", 1) if(defined($moduledata->{battery_percent}));
  3659. readingsSingleUpdate($module, "battery_percent", $moduledata->{battery_percent}, 1) if(defined($moduledata->{battery_percent}));
  3660. #readingsSingleUpdate($module, "name", encode_utf8($moduledata->{module_name}), 1) if(defined($moduledata->{module_name}));
  3661. my $setmode = "manual";
  3662. if(defined($moduledata->{setpoint}))
  3663. {
  3664. readingsSingleUpdate($module, "setpoint_mode", $moduledata->{setpoint}{setpoint_mode}, 1) if(defined($moduledata->{setpoint}{setpoint_mode}));
  3665. readingsSingleUpdate($module, "setpoint_endtime", FmtDateTime($moduledata->{setpoint}{setpoint_endtime}), 1) if(defined($moduledata->{setpoint}{setpoint_endtime}));
  3666. CommandDeleteReading( undef, "$module->{NAME} setpoint_endtime" ) if(!defined($moduledata->{setpoint}{setpoint_endtime}));
  3667. $setmode = $moduledata->{setpoint}{setpoint_mode} if(defined($moduledata->{setpoint}{setpoint_mode}));
  3668. }
  3669. readingsSingleUpdate($module, "therm_relay_cmd", $moduledata->{therm_relay_cmd}, 1) if(defined($moduledata->{therm_relay_cmd}));
  3670. if(defined($moduledata->{measured}{setpoint_temp}))
  3671. {
  3672. readingsBeginUpdate($module);
  3673. $module->{".updateTimestamp"} = FmtDateTime($moduledata->{measured}{time});
  3674. readingsBulkUpdate( $module, "setpoint_temp", $moduledata->{measured}{setpoint_temp}, 1 );
  3675. $module->{CHANGETIME}[0] = FmtDateTime($moduledata->{measured}{time});
  3676. readingsEndUpdate($module,1);
  3677. $setmode = sprintf( "%.1f", $moduledata->{measured}{setpoint_temp}) if($setmode ne "max" && $setmode ne "off");
  3678. }
  3679. readingsSingleUpdate($module, "setpoint", $setmode, 1);
  3680. my @s = ();
  3681. my $schedules = \@s;
  3682. my @schedulelist;
  3683. foreach my $scheduledata ( @{$moduledata->{therm_program_list}})
  3684. {
  3685. my $program = encode_utf8($scheduledata->{name});
  3686. $program =~ s/ /_/g;
  3687. push(@{$schedules}, [$program, $scheduledata->{program_id}]);
  3688. push(@schedulelist, $program);
  3689. if(defined($scheduledata->{selected}))
  3690. {
  3691. readingsSingleUpdate($module, "program", $program, 1);
  3692. }
  3693. }
  3694. $module->{schedules} = $schedules;
  3695. $module->{schedulenames} = join(',', @schedulelist);
  3696. }
  3697. }
  3698. #my $time = $devicedata->{time_server};
  3699. }
  3700. }
  3701. }
  3702. else
  3703. {
  3704. $hash->{status} = "error";
  3705. }
  3706. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3707. }
  3708. sub
  3709. netatmo_parseThermostatStatus($$;$)
  3710. {
  3711. my($hash, $json) = @_;
  3712. my $name = $hash->{NAME};
  3713. Log3 $name, 4, "$name: parseThermostatStatus";
  3714. my $thermostat = $modules{$hash->{TYPE}}{defptr}{"T$hash->{Thermostat}"};
  3715. if( $json ) {
  3716. Log3 $name, 5, "$name: ".Dumper($json);
  3717. $hash->{status} = $json->{status};
  3718. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3719. InternalTimer( gettimeofday() + 10, "netatmo_pollRelay", $thermostat) if($hash->{status} eq "ok");
  3720. }
  3721. else{
  3722. netatmo_pollRelay($thermostat) if($thermostat->{status} !~ /usage/ && $thermostat->{status} !~ /too_many_connections/ && $thermostat->{status} !~ /postponed/);;
  3723. }
  3724. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  3725. }
  3726. sub
  3727. netatmo_parsePublic($$)
  3728. {
  3729. my($hash, $json) = @_;
  3730. my $name = $hash->{NAME};
  3731. Log3 $name, 5, "$name: parsepublic ".Dumper($json);
  3732. if( $json ) {
  3733. $hash->{status} = $json->{status};
  3734. $hash->{status} = $json->{error}{message} if( $json->{error} );
  3735. if( $hash->{status} eq "ok" ) {
  3736. if( $hash->{Lat} && $hash->{Lon} )
  3737. {
  3738. my $found = 0;
  3739. my @readings = ();
  3740. my @readings_temperature = ();
  3741. my @readings_humidity = ();
  3742. my @readings_pressure = ();
  3743. my @readings_rain = ();
  3744. my @readings_rain_1 = ();
  3745. my @readings_rain_24 = ();
  3746. my @readings_wind_angle = ();
  3747. my @readings_wind_strength = ();
  3748. my @readings_gust_angle = ();
  3749. my @readings_gust_strength = ();
  3750. my @timestamps_temperature = ();
  3751. my @timestamps_pressure = ();
  3752. my @timestamps_rain = ();
  3753. my @timestamps_wind = ();
  3754. my @readings_altitude = ();
  3755. my @readings_latitude = ();
  3756. my @readings_longitude = ();
  3757. my $devices = $json->{body};
  3758. if( ref($devices) eq "ARRAY" ) {
  3759. foreach my $device (@{$devices}) {
  3760. $found++;
  3761. #next if( $device->{_id} ne $hash->{Device} );
  3762. next if( ref($device->{measures}) ne "HASH" );
  3763. if(defined($device->{place}))
  3764. {
  3765. push(@readings_altitude, $device->{place}{altitude}) if(defined($device->{place}{altitude}));
  3766. push(@readings_latitude, $device->{place}{location}[1]) if(defined($device->{place}{location}));
  3767. push(@readings_longitude, $device->{place}{location}[0]) if(defined($device->{place}{location}));
  3768. }
  3769. foreach my $module ( keys %{$device->{measures}}) {
  3770. #next if( ref($device->{measures}->{$module}->{res}) ne "HASH" );
  3771. if(defined($device->{measures}->{$module}->{rain_live}))
  3772. {
  3773. push(@readings_rain, $device->{measures}->{$module}->{rain_live});
  3774. push(@readings_rain_1, $device->{measures}->{$module}->{rain_60min});
  3775. push(@readings_rain_24, $device->{measures}->{$module}->{rain_24h});
  3776. push(@timestamps_rain, $device->{measures}->{$module}->{rain_timeutc});
  3777. next;
  3778. }
  3779. if(defined($device->{measures}->{$module}->{wind_strength}))
  3780. {
  3781. push(@readings_wind_angle, $device->{measures}->{$module}->{wind_angle});
  3782. push(@readings_wind_strength, $device->{measures}->{$module}->{wind_strength});
  3783. push(@readings_gust_angle, $device->{measures}->{$module}->{gust_angle});
  3784. push(@readings_gust_strength, $device->{measures}->{$module}->{gust_strength});
  3785. push(@timestamps_wind, $device->{measures}->{$module}->{wind_timeutc});
  3786. next;
  3787. }
  3788. foreach my $timestamp ( keys %{$device->{measures}->{$module}->{res}} ) {
  3789. #next if( $hash->{LAST_POLL} && $timestamp <= $hash->{LAST_POLL} );
  3790. my $i = 0;
  3791. foreach my $value ( @{$device->{measures}->{$module}->{res}->{$timestamp}} ) {
  3792. my $type = $device->{measures}->{$module}->{type}[$i];
  3793. ++$i;
  3794. if(lc($type) eq "pressure")
  3795. {
  3796. push(@timestamps_pressure, $timestamp);
  3797. push(@readings_pressure, $value);
  3798. next;
  3799. }
  3800. else
  3801. {
  3802. push(@timestamps_temperature, $timestamp) if(lc($type) eq "temperature");
  3803. push(@readings_temperature, $value) if(lc($type) eq "temperature");
  3804. push(@readings_humidity, $value) if(lc($type) eq "humidity");
  3805. next;
  3806. }
  3807. }
  3808. }
  3809. }
  3810. #$found = 1;
  3811. #last;
  3812. }
  3813. }
  3814. @readings_temperature = sort {$a <=> $b} @readings_temperature;
  3815. @readings_humidity = sort {$a <=> $b} @readings_humidity;
  3816. @readings_pressure = sort {$a <=> $b} @readings_pressure;
  3817. @readings_rain = sort {$a <=> $b} @readings_rain;
  3818. @readings_rain_1 = sort {$a <=> $b} @readings_rain_1;
  3819. @readings_rain_24 = sort {$a <=> $b} @readings_rain_24;
  3820. @readings_wind_angle = sort {$a <=> $b} @readings_wind_angle;
  3821. @readings_wind_strength = sort {$a <=> $b} @readings_wind_strength;
  3822. @readings_gust_angle = sort {$a <=> $b} @readings_gust_angle;
  3823. @readings_gust_strength = sort {$a <=> $b} @readings_gust_strength;
  3824. @timestamps_temperature = sort {$a <=> $b} @timestamps_temperature;
  3825. @timestamps_pressure = sort {$a <=> $b} @timestamps_pressure;
  3826. @timestamps_rain = sort {$a <=> $b} @timestamps_rain;
  3827. @timestamps_wind = sort {$a <=> $b} @timestamps_wind;
  3828. @readings_altitude = sort {$a <=> $b} @readings_altitude;
  3829. @readings_latitude = sort {$a <=> $b} @readings_latitude;
  3830. @readings_longitude = sort {$a <=> $b} @readings_longitude;
  3831. if(scalar(@readings_temperature) > 4)
  3832. {
  3833. for (my $i=0;$i<scalar(@readings_temperature)/10;$i++)
  3834. {
  3835. pop @readings_temperature;
  3836. pop @readings_humidity;
  3837. pop @timestamps_temperature;
  3838. shift @readings_temperature;
  3839. shift @readings_humidity;
  3840. shift @timestamps_temperature;
  3841. }
  3842. }
  3843. if(scalar(@readings_pressure) > 4)
  3844. {
  3845. for (my $i=0;$i<scalar(@readings_pressure)/10;$i++)
  3846. {
  3847. pop @readings_pressure;
  3848. pop @timestamps_pressure;
  3849. shift @readings_pressure;
  3850. shift @timestamps_pressure;
  3851. }
  3852. }
  3853. if(scalar(@readings_rain) > 4)
  3854. {
  3855. for (my $i=0;$i<scalar(@readings_rain)/20;$i++)
  3856. {
  3857. pop @readings_rain;
  3858. pop @readings_rain_1;
  3859. pop @readings_rain_24;
  3860. pop @timestamps_rain;
  3861. shift @readings_rain;
  3862. shift @readings_rain_1;
  3863. shift @readings_rain_24;
  3864. shift @timestamps_rain;
  3865. }
  3866. }
  3867. if(scalar(@readings_wind_strength) > 4)
  3868. {
  3869. for (my $i=0;$i<scalar(@readings_wind_strength)/25;$i++)
  3870. {
  3871. pop @readings_wind_strength;
  3872. pop @readings_gust_strength;
  3873. pop @timestamps_wind;
  3874. shift @readings_wind_strength;
  3875. shift @readings_gust_strength;
  3876. shift @timestamps_wind;
  3877. }
  3878. }
  3879. if(scalar(@readings_pressure) > 4)
  3880. {
  3881. for (my $i=0;$i<scalar(@readings_pressure)/20;$i++)
  3882. {
  3883. pop @readings_altitude;
  3884. pop @readings_latitude;
  3885. pop @readings_longitude;
  3886. shift @readings_altitude;
  3887. shift @readings_latitude;
  3888. shift @readings_longitude;
  3889. }
  3890. }
  3891. my $avg_temperature = 0;
  3892. my $min_temperature = 100;
  3893. my $max_temperature = -100;
  3894. foreach my $val (@readings_temperature)
  3895. {
  3896. $avg_temperature += $val / scalar(@readings_temperature);
  3897. $min_temperature = $val if($val < $min_temperature);
  3898. $max_temperature = $val if($val > $max_temperature);
  3899. }
  3900. my $avg_humidity = 0;
  3901. my $min_humidity = 100;
  3902. my $max_humidity = -100;
  3903. foreach my $val (@readings_humidity)
  3904. {
  3905. $avg_humidity += $val / scalar(@readings_humidity);
  3906. $min_humidity = $val if($val < $min_humidity);
  3907. $max_humidity = $val if($val > $max_humidity);
  3908. }
  3909. my $avgtime_temperature = 0;
  3910. foreach my $val (@timestamps_temperature)
  3911. {
  3912. $avgtime_temperature += $val / scalar(@timestamps_temperature);
  3913. }
  3914. my $avg_pressure = 0;
  3915. my $min_pressure = 2000;
  3916. my $max_pressure = -2000;
  3917. foreach my $val (@readings_pressure)
  3918. {
  3919. $avg_pressure += $val / scalar(@readings_pressure);
  3920. $min_pressure = $val if($val < $min_pressure);
  3921. $max_pressure = $val if($val > $max_pressure);
  3922. }
  3923. my $avgtime_pressure = 0;
  3924. foreach my $val (@timestamps_pressure)
  3925. {
  3926. $avgtime_pressure += $val / scalar(@timestamps_pressure);
  3927. }
  3928. my $avg_rain = 0;
  3929. my $min_rain = 1000;
  3930. my $max_rain = -1000;
  3931. foreach my $val (@readings_rain)
  3932. {
  3933. $avg_rain += $val / scalar(@readings_rain);
  3934. $min_rain = $val if($val < $min_rain);
  3935. $max_rain = $val if($val > $max_rain);
  3936. }
  3937. my $avg_rain_1 = 0;
  3938. my $min_rain_1 = 1000;
  3939. my $max_rain_1 = -1000;
  3940. foreach my $val (@readings_rain_1)
  3941. {
  3942. $avg_rain_1 += $val / scalar(@readings_rain_1);
  3943. $min_rain_1 = $val if($val < $min_rain_1);
  3944. $max_rain_1 = $val if($val > $max_rain_1);
  3945. }
  3946. my $avg_rain_24 = 0;
  3947. my $min_rain_24 = 1000;
  3948. my $max_rain_24 = -1000;
  3949. foreach my $val (@readings_rain_24)
  3950. {
  3951. $avg_rain_24 += $val / scalar(@readings_rain_24);
  3952. $min_rain_24 = $val if($val < $min_rain_24);
  3953. $max_rain_24 = $val if($val > $max_rain_24);
  3954. }
  3955. my $avgtime_rain = 0;
  3956. foreach my $val (@timestamps_rain)
  3957. {
  3958. $avgtime_rain += $val / scalar(@timestamps_rain);
  3959. }
  3960. my $avg_wind = 0;
  3961. my $min_wind = 100;
  3962. my $max_wind = -100;
  3963. foreach my $val (@readings_wind_strength)
  3964. {
  3965. $avg_wind += $val / scalar(@readings_wind_strength);
  3966. $min_wind = $val if($val < $min_wind);
  3967. $max_wind = $val if($val > $max_wind);
  3968. }
  3969. my $avg_gust = 0;
  3970. my $min_gust = 100;
  3971. my $max_gust = -100;
  3972. foreach my $val (@readings_gust_strength)
  3973. {
  3974. $avg_gust += $val / scalar(@readings_gust_strength);
  3975. $min_gust = $val if($val < $min_gust);
  3976. $max_gust = $val if($val > $max_gust);
  3977. }
  3978. my $angle_wind_x = 0;
  3979. my $angle_wind_y = 0;
  3980. foreach my $val (@readings_wind_angle)
  3981. {
  3982. next if($val == -1);
  3983. Log3 $name, 5, "$name: wind angle ".$val;
  3984. $angle_wind_x += cos($val);
  3985. $angle_wind_y += sin($val);
  3986. }
  3987. my $angle_wind = atan2($angle_wind_x,$angle_wind_y);
  3988. $angle_wind = ($angle_wind >= 0 ? $angle_wind : (2* pi + $angle_wind)) * 180/ pi;
  3989. Log3 $name, 4, "$name: wind angle avg ".$angle_wind;
  3990. my $angle_gust_x = 0;
  3991. my $angle_gust_y = 0;
  3992. foreach my $val (@readings_gust_angle)
  3993. {
  3994. next if($val == -1);
  3995. Log3 $name, 5, "$name: gust angle ".$val;
  3996. $angle_gust_x += cos($val);
  3997. $angle_gust_y += sin($val);
  3998. }
  3999. my $angle_gust = atan2($angle_gust_x,$angle_gust_y);
  4000. $angle_gust = ($angle_gust >= 0 ? $angle_gust : (2* pi + $angle_gust)) * 180/ pi;
  4001. Log3 $name, 4, "$name: gust angle avg ".$angle_gust;
  4002. my $avgtime_wind = 0;
  4003. foreach my $val (@timestamps_wind)
  4004. {
  4005. $avgtime_wind += $val / scalar(@timestamps_wind);
  4006. }
  4007. my $avg_altitude = 0;
  4008. my $min_altitude = 10000;
  4009. my $max_altitude = -10000;
  4010. foreach my $val (@readings_altitude)
  4011. {
  4012. $avg_altitude += $val / scalar(@readings_altitude);
  4013. $min_altitude = $val if($val < $min_altitude);
  4014. $max_altitude = $val if($val > $max_altitude);
  4015. }
  4016. my $avg_latitude = 0;
  4017. foreach my $val (@readings_latitude)
  4018. {
  4019. $avg_latitude += $val / scalar(@readings_latitude);
  4020. }
  4021. my $avg_longitude = 0;
  4022. foreach my $val (@readings_longitude)
  4023. {
  4024. $avg_longitude += $val / scalar(@readings_longitude);
  4025. }
  4026. $avg_temperature = sprintf( "%.2f", $avg_temperature );
  4027. $avg_humidity = sprintf( "%.2f", $avg_humidity );
  4028. $avg_pressure = sprintf( "%.2f", $avg_pressure );
  4029. $avg_rain = sprintf( "%.2f", $avg_rain );
  4030. $avg_rain_1 = sprintf( "%.2f", $avg_rain_1 );
  4031. $avg_rain_24 = sprintf( "%.2f", $avg_rain_24 );
  4032. $avg_wind = sprintf( "%.1f", $avg_wind );
  4033. $avg_gust = sprintf( "%.1f", $avg_gust );
  4034. $angle_wind = sprintf( "%i", $angle_wind );
  4035. $angle_gust = sprintf( "%i", $angle_gust );
  4036. $avgtime_temperature = sprintf( "%i", $avgtime_temperature );
  4037. $avgtime_pressure = sprintf( "%i", $avgtime_pressure );
  4038. $avgtime_rain = sprintf( "%i", $avgtime_rain );
  4039. $avgtime_wind = sprintf( "%i", $avgtime_wind );
  4040. $avg_altitude = sprintf( "%.2f", $avg_altitude );
  4041. $avg_latitude = sprintf( "%.8f", $avg_latitude );
  4042. $avg_longitude = sprintf( "%.8f", $avg_longitude );
  4043. if(scalar(@readings_temperature) > 0)
  4044. {
  4045. push(@readings, [$avgtime_temperature, 'temperature', $avg_temperature]);
  4046. push(@readings, [$avgtime_temperature, 'temperature_min', $min_temperature]);
  4047. push(@readings, [$avgtime_temperature, 'temperature_max', $max_temperature]);
  4048. push(@readings, [$avgtime_temperature, 'humidity', $avg_humidity]);
  4049. push(@readings, [$avgtime_temperature, 'humidity_min', $min_humidity]);
  4050. push(@readings, [$avgtime_temperature, 'humidity_max', $max_humidity]);
  4051. }
  4052. if(scalar(@readings_pressure) > 0)
  4053. {
  4054. push(@readings, [$avgtime_pressure, 'pressure', $avg_pressure]);
  4055. push(@readings, [$avgtime_pressure, 'pressure_min', $min_pressure]);
  4056. push(@readings, [$avgtime_pressure, 'pressure_max', $max_pressure]);
  4057. }
  4058. if(scalar(@readings_rain) > 0)
  4059. {
  4060. push(@readings, [$avgtime_rain, 'rain', $avg_rain]);
  4061. push(@readings, [$avgtime_rain, 'rain_min', $min_rain]);
  4062. push(@readings, [$avgtime_rain, 'rain_max', $max_rain]);
  4063. push(@readings, [$avgtime_rain, 'rain_hour', $avg_rain_1]);
  4064. push(@readings, [$avgtime_rain, 'rain_hour_min', $min_rain_1]);
  4065. push(@readings, [$avgtime_rain, 'rain_hour_max', $max_rain_1]);
  4066. push(@readings, [$avgtime_rain, 'rain_day', $avg_rain_24]);
  4067. push(@readings, [$avgtime_rain, 'rain_day_min', $min_rain_24]);
  4068. push(@readings, [$avgtime_rain, 'rain_day_max', $max_rain_24]);
  4069. }
  4070. if(scalar(@readings_wind_strength) > 0)
  4071. {
  4072. push(@readings, [$avgtime_wind, 'wind', $avg_wind]);
  4073. push(@readings, [$avgtime_wind, 'wind_min', $min_wind]);
  4074. push(@readings, [$avgtime_wind, 'wind_max', $max_wind]);
  4075. push(@readings, [$avgtime_wind, 'gust', $avg_gust]);
  4076. push(@readings, [$avgtime_wind, 'gust_min', $min_gust]);
  4077. push(@readings, [$avgtime_wind, 'gust_max', $max_gust]);
  4078. push(@readings, [$avgtime_wind, 'wind_angle', $angle_wind]);
  4079. push(@readings, [$avgtime_wind, 'gust_angle', $angle_gust]);
  4080. }
  4081. if(scalar(@readings_altitude) > 0)
  4082. {
  4083. $hash->{altitude} = $avg_altitude;
  4084. $hash->{location} = $avg_latitude.",".$avg_longitude;
  4085. }
  4086. $hash->{stations_indoor} = scalar(@readings_pressure);
  4087. $hash->{stations_outdoor} = scalar(@readings_temperature);
  4088. $hash->{stations_rain} = scalar(@readings_rain);
  4089. $hash->{stations_wind} = scalar(@readings_wind_strength);
  4090. my (undef,$latest) = netatmo_updateReadings( $hash, \@readings );
  4091. $hash->{LAST_POLL} = FmtDateTime( $latest ) if( @readings );
  4092. #$hash->{STATE} = "Error: device not found" if( !$found );
  4093. } else {
  4094. return $json->{body};
  4095. }
  4096. } #else {
  4097. #return $hash->{status};
  4098. #}
  4099. }
  4100. else
  4101. {
  4102. $hash->{status} = "error";
  4103. }
  4104. readingsSingleUpdate( $hash, "active", $hash->{status}, 1 ) if($hash->{status} ne "no data");
  4105. }
  4106. sub
  4107. netatmo_parseAddress($$)
  4108. {
  4109. my($hash, $json) = @_;
  4110. my $name = $hash->{NAME};
  4111. Log3 $name, 4, "$name: parseAddress";
  4112. if( $json ) {
  4113. Log3 $name, 5, "$name: ".Dumper($json);
  4114. $hash->{status} = $json->{status};
  4115. $hash->{status} = $json->{error}{message} if( $json->{error} );
  4116. if( $hash->{status} eq "OK" ) {
  4117. if( $json->{results} ) {
  4118. return $json->{results}->[0]->{formatted_address};
  4119. }
  4120. } else {
  4121. return $hash->{status};
  4122. }
  4123. }
  4124. }
  4125. sub
  4126. netatmo_parseLatLng($$)
  4127. {
  4128. my($hash, $json) = @_;
  4129. my $name = $hash->{NAME};
  4130. Log3 $name, 4, "$name: parseLatLng";
  4131. if( $json ) {
  4132. Log3 $name, 5, "$name: ".Dumper($json);
  4133. $hash->{status} = $json->{status};
  4134. $hash->{status} = $json->{error}{message} if( $json->{error} );
  4135. if( $hash->{status} eq "OK" ) {
  4136. if( $json->{results} ) {
  4137. return $json->{results}->[0]->{geometry}->{bounds};
  4138. }
  4139. } else {
  4140. return $hash->{status};
  4141. }
  4142. }
  4143. }
  4144. sub
  4145. netatmo_pollDevice($)
  4146. {
  4147. my ($hash) = @_;
  4148. my $name = $hash->{NAME};
  4149. $hash->{openRequests} = 0 if ( !defined( $hash->{openRequests}) );
  4150. if( $hash->{Module} )
  4151. {
  4152. my @types = split( ' ', $hash->{dataTypes} ) if(defined($hash->{dataTypes}));
  4153. Log3 $name, 4, "$name: pollDevice types [".$hash->{dataTypes} . "] for modules [".$hash->{Module}."]" if(defined($hash->{dataTypes}));
  4154. my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef );
  4155. $lastupdate = (time-7*24*60*60) if(!$lastupdate and !$hash->{model});
  4156. $hash->{openRequests} += int(@types);
  4157. $hash->{openRequests} += 1 if(int(@types)==0);
  4158. foreach my $module (split( ' ', $hash->{Module} ) ) {
  4159. my $type;
  4160. $type = shift(@types) if( $module and @types);
  4161. readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0) if($type);
  4162. netatmo_requestDeviceReadings( $hash, $hash->{Device}, $type, $module ne $hash->{Device}?$module:undef );# if($type);
  4163. }
  4164. }
  4165. elsif( defined($hash->{Lat}) )
  4166. {
  4167. #$hash->{openRequests} += 1;
  4168. netatmo_getPublicDevices($hash, 0, $hash->{Lat}, $hash->{Lon}, $hash->{Rad} );
  4169. } elsif( $hash->{Device} ) {
  4170. $hash->{openRequests} += 1;
  4171. netatmo_requestDeviceReadings( $hash, $hash->{Device} );
  4172. }
  4173. }
  4174. sub
  4175. netatmo_pollThermostat($)
  4176. {
  4177. my ($hash) = @_;
  4178. my $name = $hash->{NAME};
  4179. $hash->{openRequests} = 0 if ( !defined( $hash->{openRequests}) );
  4180. if( $hash->{Thermostat} )
  4181. {
  4182. Log3 $name, 4, "$name: pollThermostat types [".$hash->{dataTypes} . "] for thermostat [".$hash->{Thermostat}."]" if(defined($hash->{dataTypes}));
  4183. my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef );
  4184. $lastupdate = (time-7*24*60*60) if(!$lastupdate and !$hash->{model});
  4185. $hash->{openRequests} += 1;
  4186. my $type = $hash->{dataTypes};
  4187. readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0) if($type);
  4188. netatmo_requestDeviceReadings( $hash, $hash->{Relay}, $type, $hash->{Thermostat} );# if($type);
  4189. }
  4190. elsif( $hash->{Relay} )
  4191. {
  4192. $hash->{openRequests} += 1;
  4193. netatmo_requestDeviceReadings( $hash, $hash->{Relay} );
  4194. }
  4195. }
  4196. sub
  4197. netatmo_pollGlobal($)
  4198. {
  4199. my ($hash) = @_;
  4200. my $name = $hash->{NAME};
  4201. netatmo_refreshToken($hash, defined($hash->{access_token}));
  4202. return undef if(!defined($hash->{access_token}));
  4203. Log3 $name, 4, "$name: pollGlobal";
  4204. HttpUtils_NonblockingGet({
  4205. url => "https://api.netatmo.com/api/getstationsdata",
  4206. timeout => 20,
  4207. noshutdown => 1,
  4208. data => { access_token => $hash->{access_token}, },
  4209. hash => $hash,
  4210. type => 'stationsdata',
  4211. callback => \&netatmo_dispatch,
  4212. });
  4213. return undef;
  4214. }
  4215. sub
  4216. netatmo_pollGlobalHealth($)
  4217. {
  4218. my ($hash) = @_;
  4219. my $name = $hash->{NAME};
  4220. netatmo_refreshToken($hash, defined($hash->{access_token}));
  4221. return undef if(!defined($hash->{access_token}));
  4222. Log3 $name, 4, "$name: pollGlobalHealth";
  4223. HttpUtils_NonblockingGet({
  4224. url => "https://api.netatmo.com/api/gethomecoachsdata",
  4225. timeout => 20,
  4226. noshutdown => 1,
  4227. data => { access_token => $hash->{access_token}, },
  4228. hash => $hash,
  4229. type => 'stationsdata',
  4230. callback => \&netatmo_dispatch,
  4231. });
  4232. return undef;
  4233. }
  4234. sub
  4235. netatmo_pollForecast($)
  4236. {
  4237. my ($hash) = @_;
  4238. my $name = $hash->{NAME};
  4239. if(!defined($hash->{Station}))
  4240. {
  4241. Log3 $name, 1, "$name: device missing the definition! please redefine it.";
  4242. return undef;
  4243. }
  4244. return undef if( !defined($hash->{IODev}) );
  4245. my $iohash = $hash->{IODev};
  4246. netatmo_refreshAppToken($iohash, defined($iohash->{access_token_app}));
  4247. return undef if(!defined($iohash->{access_token_app}));
  4248. if(!defined($iohash->{access_token_app}))
  4249. {
  4250. Log3 $name, 1, "$name: pollForecast - missing app token!";
  4251. return undef;
  4252. }
  4253. Log3 $name, 4, "$name: pollForecast (forecastdata)";
  4254. HttpUtils_NonblockingGet({
  4255. url => "https://api.netatmo.com/api/simplifiedfuturemeasure",
  4256. timeout => 20,
  4257. noshutdown => 1,
  4258. data => { device_id => $hash->{Station}, },
  4259. header => "Authorization: Bearer ".$iohash->{access_token_app},
  4260. hash => $hash,
  4261. type => 'forecastdata',
  4262. callback => \&netatmo_dispatch,
  4263. });
  4264. return undef;
  4265. }
  4266. sub
  4267. netatmo_pollHome($)
  4268. {
  4269. my ($hash) = @_;
  4270. my $name = $hash->{NAME};
  4271. Log3 $name, 3, "$name: pollHome (".$hash->{Home}.")";
  4272. if( $hash->{Home} ) {
  4273. return undef if($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/ || $hash->{status} =~ /postponed/);
  4274. my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef );
  4275. $lastupdate = (time-7*24*60*60) if(!$lastupdate);
  4276. readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0);
  4277. netatmo_requestHomeReadings( $hash, $hash->{Home} );
  4278. }
  4279. }
  4280. sub
  4281. netatmo_pollRelay($)
  4282. {
  4283. my ($hash) = @_;
  4284. my $name = $hash->{NAME};
  4285. Log3 $name, 5, "$name: pollRelay (".$hash->{Relay}.")";
  4286. if( $hash->{Relay} ) {
  4287. return undef if(defined($hash->{status}) && ($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/ || $hash->{status} =~ /postponed/));
  4288. my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef );
  4289. $lastupdate = (time-7*24*60*60) if(!$lastupdate);
  4290. readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0);
  4291. netatmo_requestThermostatReadings( $hash, $hash->{Relay} );
  4292. }
  4293. }
  4294. sub
  4295. netatmo_pollPerson($)
  4296. {
  4297. my ($hash) = @_;
  4298. my $name = $hash->{NAME};
  4299. Log3 $name, 5, "$name: pollPerson";
  4300. return undef if(defined($hash->{status}) && ($hash->{status} =~ /usage/ || $hash->{status} =~ /too_many_connections/ || $hash->{status} =~ /postponed/));
  4301. if( $hash->{Home} ) {
  4302. my $lastupdate = ReadingsVal( $hash->{NAME}, ".lastupdate", undef );
  4303. $lastupdate = (time-7*24*60*60) if(!$lastupdate);
  4304. readingsSingleUpdate($hash, ".lastupdate", $lastupdate, 0);
  4305. netatmo_requestPersonReadings( $hash );
  4306. }
  4307. }
  4308. sub
  4309. netatmo_Get($$@)
  4310. {
  4311. my ($hash, $name, $cmd, @args) = @_;
  4312. my $list = "";
  4313. if( $hash->{SUBTYPE} eq "DEVICE"
  4314. || $hash->{SUBTYPE} eq "MODULE"
  4315. || $hash->{SUBTYPE} eq "PUBLIC"
  4316. || $hash->{SUBTYPE} eq "FORECAST"
  4317. || $hash->{SUBTYPE} eq "HOME"
  4318. || $hash->{SUBTYPE} eq "CAMERA"
  4319. || $hash->{SUBTYPE} eq "PERSON"
  4320. || $hash->{SUBTYPE} eq "RELAY"
  4321. || $hash->{SUBTYPE} eq "THERMOSTAT" ) {
  4322. $list = "update:noArg";
  4323. $list = " ping:noArg video video_local live live_local snapshot" if($hash->{SUBTYPE} eq "CAMERA");
  4324. $list .= " config:noArg timelapse:noArg" if($hash->{SUBTYPE} eq "CAMERA" && defined($hash->{model}) && $hash->{model} eq "NOC");
  4325. #$list .= " weathericon" if($hash->{SUBTYPE} eq "FORECAST");
  4326. if( $cmd eq "weathericon" ) {
  4327. return "no weather code was passed" if($args[0] eq "");
  4328. return netatmo_weatherIcon();
  4329. }
  4330. if( $cmd eq "ping" ) {
  4331. netatmo_pingCamera($hash);
  4332. return undef;
  4333. }
  4334. if( $cmd eq "video" || $cmd eq "video_local" ) {
  4335. return "no video_id was passed" if(!defined($args[0]) || $args[0] eq "");
  4336. return netatmo_getCameraVideo($hash,$args[0],$cmd);
  4337. }
  4338. elsif( $cmd eq "live" || $cmd eq "live_local" ) {
  4339. return netatmo_getCameraLive($hash,$cmd);
  4340. }
  4341. elsif($cmd eq "timelapse") {
  4342. return netatmo_getCameraTimelapse($hash);
  4343. }
  4344. elsif( $cmd eq "snapshot" ) {
  4345. return netatmo_getCameraSnapshot($hash);
  4346. }
  4347. elsif( $cmd eq "config" ) {
  4348. return netatmo_getPresenceConfig($hash);
  4349. }
  4350. if( $cmd eq "updateAll" ) {
  4351. $cmd = "update";
  4352. CommandDeleteReading( undef, "$name .*" );
  4353. }
  4354. if( $cmd eq "update" ) {
  4355. netatmo_poll($hash);
  4356. return undef;
  4357. }
  4358. } elsif( $hash->{SUBTYPE} eq "ACCOUNT" ) {
  4359. $list = "update:noArg devices:noArg homes:noArg thermostats:noArg homecoachs:noArg public showAccount:noArg";
  4360. if( $cmd eq "update" ) {
  4361. netatmo_poll($hash);
  4362. return undef;
  4363. }
  4364. if( $cmd eq 'showAccount' )
  4365. {
  4366. my $username = $hash->{helper}{username};
  4367. my $password = $hash->{helper}{password};
  4368. return 'no username set' if( !$username );
  4369. return 'no password set' if( !$password );
  4370. $username = netatmo_decrypt( $username );
  4371. $password = netatmo_decrypt( $password );
  4372. return "username: $username\npassword: $password";
  4373. }
  4374. if( $cmd eq "devices" ) {
  4375. my $devices = netatmo_getDevices($hash,1);
  4376. my $ret;
  4377. foreach my $device (@{$devices}) {
  4378. $ret .= "\n" if( $ret );
  4379. $ret .= "$device->{_id}\t$device->{firmware}\t$device->{type}\t".($device->{type} !~ /Module/ ? "\t" : "")."$device->{module_name}";
  4380. }
  4381. $ret = "id\t\t\tfw\ttype\t\tname\n" . $ret if( $ret );
  4382. $ret = "no devices found" if( !$ret );
  4383. return $ret;
  4384. } elsif( $cmd eq "homes" ) {
  4385. my $homes = netatmo_getHomes($hash,1);
  4386. Log3 $name, 5, "$name: homes ".Dumper($homes);
  4387. my $ret;
  4388. foreach my $home (@{$homes}) {
  4389. $ret .= "\n" if( $ret );
  4390. $ret .= "$home->{id} \t\tHome\t".encode_utf8($home->{name}) if(defined($home->{cameras}));
  4391. if(defined($home->{status}))
  4392. {
  4393. $ret .= "$home->{id} \t\t\tCamera\t".encode_utf8($home->{name});
  4394. foreach my $tag (@{$home->{modules}}) {
  4395. $ret .= "\n$tag->{id} \t\t\tTag\t".encode_utf8($tag->{name}) if(defined($tag->{name}));
  4396. }
  4397. }
  4398. $ret .= "$home->{id} \tPerson\t".encode_utf8($home->{pseudo}) if(defined($home->{pseudo}));
  4399. $ret .= "$home->{id} \tPerson\t(Unknown)" if(defined($home->{face}) && !defined($home->{pseudo}));
  4400. }
  4401. $ret = "id\t\t\t\t\ttype\tname\n" . $ret if( $ret );
  4402. $ret = "no homes found" if( !$ret );
  4403. return $ret;
  4404. } elsif( $cmd eq "thermostats" ) {
  4405. my $thermostats = netatmo_getThermostats($hash,1);
  4406. Log3 $name, 5, "$name: thermostats ".Dumper($thermostats);
  4407. my $ret;
  4408. foreach my $thermostat (@{$thermostats}) {
  4409. $ret .= "\n" if( $ret );
  4410. $ret .= "$thermostat->{_id}\t$thermostat->{firmware}\t$thermostat->{type}\t ".(defined($thermostat->{module_name}) ? $thermostat->{module_name} : $thermostat->{station_name});
  4411. }
  4412. $ret = "id\t\t\tfw\ttype\t name\n" . $ret if( $ret );
  4413. $ret = "no thermostats found" if( !$ret );
  4414. return $ret;
  4415. } elsif( $cmd eq "homecoachs" ) {
  4416. my $homecoachs = netatmo_getHomecoachs($hash,1);
  4417. Log3 $name, 5, "$name: homecoachs ".Dumper($homecoachs);
  4418. my $ret;
  4419. foreach my $homecoach (@{$homecoachs}) {
  4420. $ret .= "\n" if( $ret );
  4421. $ret .= "$homecoach->{_id}\t$homecoach->{firmware}\t$homecoach->{type}\t$homecoach->{name}";
  4422. }
  4423. $ret = "id\t\t\tfw\ttype\t name\n" . $ret if( $ret );
  4424. $ret = "no homecoachs found" if( !$ret );
  4425. return $ret;
  4426. } elsif( $cmd eq "public" ) {
  4427. my $station = '';
  4428. my $addr = '';
  4429. $station = shift @args if( $args[0] && $args[0] =~ m/[\da-f]{2}(:[\da-f]{2}){5}/ );
  4430. if( @args && defined($args[0]) && ( $args[0] =~ m/^\d{5}$/
  4431. || $args[0] =~ m/^a:/ ) ) {
  4432. $addr = shift @args;
  4433. $addr = substr($addr,2) if( $addr =~ m/^a:/ );
  4434. my $bounds = netatmo_getLatLong( $hash,1,$addr );
  4435. $args[0] = $bounds->{northeast}->{lat};
  4436. $args[1] = $bounds->{northeast}->{lng};
  4437. $args[2] = $bounds->{southwest}->{lat};
  4438. $args[3] = $bounds->{southwest}->{lng};
  4439. } elsif(defined($args[0]) && $args[0] =~ m/,/) {
  4440. my @latlon1 = split( ',', $args[0] );
  4441. if($args[1] !~ m/,/){
  4442. $args[3] = undef;
  4443. $args[2] = $args[1];
  4444. } else {
  4445. my @latlon2 = split( ',', $args[1] );
  4446. $args[3] = $latlon2[1];
  4447. $args[2] = $latlon2[0];
  4448. }
  4449. $args[1] = $latlon1[1];
  4450. $args[0] = $latlon1[0];
  4451. }
  4452. my $devices = netatmo_getPublicDevices($hash, 1, $args[0], $args[1], $args[2], $args[3] );
  4453. my $ret;
  4454. my $addresscount = 0;
  4455. if( ref($devices) eq "ARRAY" ) {
  4456. my $csrftoken = (defined($FW_CSRF) ? $FW_CSRF : "&nocsrf");
  4457. foreach my $device (@{$devices}) {
  4458. next if(!defined($device->{_id}));
  4459. next if( $station && $station ne $device->{_id} );
  4460. my $idname = $device->{_id};
  4461. $idname =~ s/:/_/g;
  4462. next if(AttrVal("netatmo_D".$idname, "IODev", undef));
  4463. next if(AttrVal("netatmo_D".$device->{_id}, "IODev", undef));
  4464. next if(defined($modules{$hash->{TYPE}}{defptr}{"D$idname"}));
  4465. next if(defined($modules{$hash->{TYPE}}{defptr}{"D$device->{_id}"}));
  4466. $ret .= "\n" if( $ret );
  4467. $ret .= sprintf( "%s<a href=\"https://www.google.com/maps/@%.8f,%.8f,19z\" target=\"gmaps\"> %.6f,%.6f %i m</a>", $device->{_id},
  4468. $device->{place}->{location}->[1], $device->{place}->{location}->[0],
  4469. $device->{place}->{location}->[1], $device->{place}->{location}->[0],
  4470. $device->{place}->{altitude} );
  4471. #$ret .= "\t";
  4472. $addr = '';
  4473. $addr .= netatmo_getAddress( $hash, 1, $device->{place}->{location}->[1], $device->{place}->{location}->[0] ) if($addresscount++ < AttrVal($name,"addresslimit",10));
  4474. $addr .= " (address limit reached, " if($addresscount == AttrVal($name,"addresslimit",10)+2);
  4475. $addr .= " change attribute addresslimit to see more) " if($addresscount == AttrVal($name,"addresslimit",10)+3);
  4476. next if( ref($device->{measures}) ne "HASH" );
  4477. my $ext;
  4478. my $got_temp;
  4479. my $got_press;
  4480. foreach my $module ( sort keys %{$device->{measures}}) {
  4481. next if( ref($device->{measures}->{$module}->{res}) ne "HASH" );
  4482. $ext .= "$module ";
  4483. $ext .= join(',', @{$device->{measures}->{$module}->{type}});
  4484. $ext .= " ";
  4485. foreach my $timestamp ( keys %{$device->{measures}->{$module}->{res}} ) {
  4486. my $i = 0;
  4487. foreach my $value ( @{$device->{measures}->{$module}->{res}->{$timestamp}} ) {
  4488. my $type = $device->{measures}->{$module}->{type}[$i];
  4489. if( $type eq "temperature" ) {
  4490. $ret .= "\t";
  4491. $ret .= " " if(int($value)<10);
  4492. $ret .= sprintf( "%.1f \xc2\xb0C", $value );
  4493. $got_temp = 1;
  4494. } elsif( $type eq "humidity" ) {
  4495. $ret .= "\t";
  4496. $ret .= " " if(int($value)<10);
  4497. $value = 99 if(int($value)>99);
  4498. $ret .= sprintf( "%i %%", $value );
  4499. } elsif( $type eq "pressure" ) {
  4500. $ret .= "\t\t" if( !$got_temp );
  4501. $ret .= "\t";
  4502. $ret .= " " if(int($value)<1000);
  4503. $ret .= sprintf( "%i hPa", $value );
  4504. $got_press = 1;
  4505. } elsif( $type eq "rain" ) {
  4506. $ret .= "\t" if( !$got_temp );
  4507. $ret .= "\t\t" if( !$got_press );
  4508. #$ret .= "\t";
  4509. $ret .= " ";
  4510. $ret .= " " if(int($value)<10);
  4511. $ret .= sprintf( "%i mm", $value );
  4512. }
  4513. else
  4514. {
  4515. Log3 $name, 2, "$name: unknown type ".$type;
  4516. }
  4517. ++$i;
  4518. }
  4519. last;
  4520. }
  4521. }
  4522. my $got_rain = 0;
  4523. foreach my $module ( keys %{$device->{measures}}) {
  4524. my $value = $device->{measures}->{$module}->{rain_24h};
  4525. if( defined($value) ) {
  4526. $got_rain = 1;
  4527. $ext .= "$module ";
  4528. $ext .= "rain";
  4529. $ext .= " ";
  4530. if( defined($value) )
  4531. {
  4532. $ret .= "\t\t\t " if( !$got_press );
  4533. $ret .= "\t ";
  4534. $ret .= " " if(int($value)<10);
  4535. $value = 99 if(int($value)>99);
  4536. $ret .= sprintf( "%.1f mm", $value );
  4537. }
  4538. }
  4539. }
  4540. $ret .= "\t\t" if( !$got_rain );
  4541. my $got_wind = 0;
  4542. foreach my $module ( keys %{$device->{measures}}) {
  4543. my $value = $device->{measures}->{$module}->{gust_strength};
  4544. if( defined($value) ) {
  4545. $got_wind = 1;
  4546. $ext .= "$module ";
  4547. $ext .= "windstrength,windangle,guststrength,gustangle";
  4548. $ext .= " ";
  4549. if( defined($value) )
  4550. {
  4551. $ret .= " ";
  4552. $ret .= " " if(int($value)<10);
  4553. $value = 99 if(int($value)>99);
  4554. $ret .= sprintf( "%i km/h", $value );
  4555. }
  4556. }
  4557. }
  4558. $ret .= "\t" if( !$got_wind );
  4559. $ret .= "\t $addr" if(defined($addr));
  4560. #$ret .= "\n\tdefine netatmo_P$device->{_id} netatmo PUBLIC $device->{_id} $ext" if( $station );
  4561. my $definelink = "<a href=\"#\" onclick=\"javascript:window.open((\'".$FW_ME."?cmd=define netatmo_D".$idname." netatmo+++PUBLIC ".$device->{_id}." ".$ext.$csrftoken."\').replace('+++',' '), 'definewindow');\">=&gt; </a>";
  4562. $ret =~ s/$device->{_id}/$definelink/;
  4563. }
  4564. } else {
  4565. $ret = $devices if( !ref($devices) );
  4566. }
  4567. $ret = " latitude,longitude alt\ttemp\thum\tpressure\t rain\t\twind\t address\n" . $ret if( $ret );
  4568. $ret = "no devices found" if( !$ret );
  4569. return $ret;
  4570. }
  4571. }
  4572. return "Unknown argument $cmd, choose one of $list";
  4573. }
  4574. #########################
  4575. sub
  4576. netatmo_addExtension($) {
  4577. my ($hash) = @_;
  4578. my $name = $hash->{NAME};
  4579. #netatmo_removeExtension() ;
  4580. my $url = "/netatmo";
  4581. delete $data{FWEXT}{$url} if($data{FWEXT}{$url});
  4582. Log3 $name, 1, "Starting Netatmo webhook for $name";
  4583. $data{FWEXT}{$url}{deviceName} = $name;
  4584. $data{FWEXT}{$url}{FUNC} = "netatmo_Webhook";
  4585. $data{FWEXT}{$url}{LINK} = "netatmo";
  4586. netatmo_registerWebhook($hash);
  4587. }
  4588. #########################
  4589. sub
  4590. netatmo_removeExtension($) {
  4591. my ($hash) = @_;
  4592. netatmo_dropWebhook($hash);
  4593. my $url = "/netatmo";
  4594. my $name = $data{FWEXT}{$url}{deviceName};
  4595. Log3 $name, 3, "Stopping Netatmo webhook for $name";
  4596. delete $data{FWEXT}{$url};
  4597. }
  4598. sub
  4599. netatmo_registerWebhook($)
  4600. {
  4601. my ($hash) = @_;
  4602. my $name = $hash->{NAME};
  4603. return undef if( !defined($hash->{IODev}) );
  4604. my $iohash = $hash->{IODev};
  4605. netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  4606. return undef if(!defined($iohash->{access_token}));
  4607. Log3 $name, 3, "Registering Netatmo webhook";
  4608. my $webhookurl = AttrVal($name,"webhookURL",undef);
  4609. return undef if(!defined($webhookurl));
  4610. HttpUtils_NonblockingGet({
  4611. url => "https://api.netatmo.com/api/addwebhook",
  4612. timeout => 20,
  4613. noshutdown => 1,
  4614. data => { access_token => $iohash->{access_token}, url => $webhookurl, app_type => 'app_security', },
  4615. hash => $hash,
  4616. type => 'addwebhook',
  4617. callback => \&netatmo_dispatch,
  4618. });
  4619. }
  4620. sub
  4621. netatmo_dropWebhook($)
  4622. {
  4623. my ($hash) = @_;
  4624. my $name = $hash->{NAME};
  4625. return undef if( !defined($hash->{IODev}) );
  4626. my $iohash = $hash->{IODev};
  4627. netatmo_refreshToken($iohash, defined($iohash->{access_token}));
  4628. return undef if(!defined($iohash->{access_token}));
  4629. Log3 $name, 3, "Dropping Netatmo webhook";
  4630. HttpUtils_NonblockingGet({
  4631. url => "https://api.netatmo.com/api/dropwebhook",
  4632. timeout => 20,
  4633. noshutdown => 1,
  4634. data => { access_token => $iohash->{access_token}, app_type => 'app_security', },
  4635. hash => $hash,
  4636. type => 'dropwebhook',
  4637. callback => \&netatmo_dispatch,
  4638. });
  4639. }
  4640. sub
  4641. netatmo_webhookStatus($$$)
  4642. {
  4643. my($hash, $json, $hookstate) = @_;
  4644. my $name = $hash->{NAME};
  4645. Log3 $name, 4, "$name: webhookStatus ($hookstate)";
  4646. if( $json ) {
  4647. $hash->{status} = $json->{status};
  4648. $hash->{status} = $json->{error}{message} if( $json->{error} );
  4649. $hookstate = "error" if( $json->{error} );
  4650. readingsSingleUpdate($hash, "webhook", $hookstate, 1);
  4651. }
  4652. else
  4653. {
  4654. $hash->{status} = "error";
  4655. readingsSingleUpdate($hash, "webhook", "error", 1);
  4656. }
  4657. return ( "text/plain; charset=utf-8",
  4658. "JSON" );
  4659. }
  4660. sub netatmo_Webhook() {
  4661. my ($request) = @_;
  4662. my $hash = $modules{"netatmo"}{defptr}{"WEBHOOK"};
  4663. if(!defined($hash)){
  4664. Log3 "netatmo", 1, "Netatmo webhook hash not defined!";
  4665. return ( "text/plain; charset=utf-8",
  4666. "HASH" );
  4667. }
  4668. my $name = $hash->{NAME};
  4669. my ($link,$data);
  4670. if ( $request =~ m,^(\/[^/]+?)(?:\&|\?|\/\?|\/)(.*)?$, ) {
  4671. $link = $1;
  4672. $data = $2;
  4673. } else {
  4674. Log3 "netatmo", 1, "Netatmo webhook no data received!";
  4675. return ( "text/plain; charset=utf-8",
  4676. "NO" );
  4677. }
  4678. Log3 $name, 5, "Netatmo webhook JSON:\n".$data;
  4679. my $json = eval { JSON->new->utf8(0)->decode($data) };
  4680. if($@)
  4681. {
  4682. Log3 $name, 2, "$name: invalid json evaluation for webhook ".$@;
  4683. return undef;
  4684. }
  4685. readingsBeginUpdate($hash);
  4686. if(defined($json->{message})){
  4687. my $eventmessage = $json->{message};
  4688. $eventmessage =~ s/<\/b>//g;
  4689. $eventmessage =~ s/<\/b>//g;
  4690. readingsBulkUpdate( $hash, "state", $eventmessage );
  4691. }
  4692. readingsBulkUpdate( $hash, "event_type", $json->{event_type} ) if(defined($json->{event_type}));
  4693. readingsBulkUpdate( $hash, "camera_id", $json->{camera_id} ) if(defined($json->{camera_id}));
  4694. readingsBulkUpdate( $hash, "module_id", $json->{module_id} ) if(defined($json->{module_id}));
  4695. readingsBulkUpdate( $hash, "person_id", $json->{persons}[0]{id} ) if(defined($json->{persons}[0]{id}));
  4696. if(defined($json->{snapshot_id})) {
  4697. readingsBulkUpdate( $hash, "snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$json->{snapshot_id}."&key=".$json->{snapshot_key}, 1 );
  4698. }
  4699. elsif(defined($json->{persons}[0]{face_id})) {
  4700. readingsBulkUpdate( $hash, "snapshot", "https://api.netatmo.com/api/getcamerapicture?image_id=".$json->{persons}[0]{face_id}."&key=".$json->{persons}[0]{face_key}, 1 );
  4701. }
  4702. readingsEndUpdate( $hash, 1 );
  4703. if(AttrVal($name,"webhookPoll","0") eq "1" && defined($json->{home_id}))
  4704. {
  4705. my $home = $modules{$hash->{TYPE}}{defptr}{"H$json->{home_id}"};
  4706. netatmo_poll($home) if($home->{status} !~ /usage/ && $home->{status} !~ /too_many_connections/ && $home->{status} !~ /postponed/);;
  4707. }
  4708. return ( "text/plain; charset=utf-8",
  4709. "{\"status\":\"ok\"}" );
  4710. }
  4711. sub netatmo_Attr($$$)
  4712. {
  4713. my ($cmd, $name, $attrName, $attrVal) = @_;
  4714. my $orig = $attrVal;
  4715. $attrVal = int($attrVal) if($attrName eq "interval" || $attrName eq "setpoint_duration");
  4716. $attrVal = 15 if($attrName eq "setpoint_duration" && $attrVal < 15 && $attrVal != 0);
  4717. return undef if(!defined($defs{$name}));
  4718. if( $attrName eq "interval" ) {
  4719. my $hash = $defs{$name};
  4720. $attrVal = 60*5 if($hash->{SUBTYPE} ne "HOME" && $attrVal < 60*5 && $attrVal != 0);
  4721. #\$attrVal = 2700 if(($attrVal < 2700 && ($hash->{SUBTYPE} eq "ACCOUNT" || $hash->{SUBTYPE} eq "FORECAST");
  4722. $hash->{INTERVAL} = $attrVal if($attrVal);
  4723. $hash->{INTERVAL} = 60*30 if( !$hash->{INTERVAL} );
  4724. } elsif( $attrName eq "setpoint_duration" ) {
  4725. my $hash = $defs{$name};
  4726. #$hash->{SETPOINT_DURATION} = $attrVal;
  4727. #$hash->{SETPOINT_DURATION} = 60 if( !$hash->{SETPOINT_DURATION} );
  4728. } elsif( $attrName eq "disable" ) {
  4729. my $hash = $defs{$name};
  4730. RemoveInternalTimer($hash);
  4731. if( $cmd eq "set" && $attrVal ne "0" ) {
  4732. } else {
  4733. $attr{$name}{$attrName} = 0;
  4734. netatmo_poll($hash);
  4735. }
  4736. }
  4737. if( $cmd eq "set" ) {
  4738. if( $orig ne $attrVal ) {
  4739. $attr{$name}{$attrName} = $attrVal;
  4740. return $attrName ." set to ". $attrVal;
  4741. }
  4742. }
  4743. return;
  4744. }
  4745. sub netatmo_encrypt($)
  4746. {
  4747. my ($decoded) = @_;
  4748. my $key = getUniqueId();
  4749. my $encoded;
  4750. return $decoded if( $decoded =~ /crypt:/ );
  4751. for my $char (split //, $decoded) {
  4752. my $encode = chop($key);
  4753. $encoded .= sprintf("%.2x",ord($char)^ord($encode));
  4754. $key = $encode.$key;
  4755. }
  4756. return 'crypt:'.$encoded;
  4757. }
  4758. sub netatmo_decrypt($)
  4759. {
  4760. my ($encoded) = @_;
  4761. my $key = getUniqueId();
  4762. my $decoded;
  4763. return $encoded if( $encoded !~ /crypt:/ );
  4764. $encoded = $1 if( $encoded =~ /crypt:(.*)/ );
  4765. for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) {
  4766. my $decode = chop($key);
  4767. $decoded .= chr(ord($char)^ord($decode));
  4768. $key = $decode.$key;
  4769. }
  4770. return $decoded;
  4771. }
  4772. ##########################
  4773. sub netatmo_DbLog_splitFn($)
  4774. {
  4775. my ($event) = @_;
  4776. my ($reading, $value, $unit) = "";
  4777. my @parts = split(/ /,$event,3);
  4778. $reading = $parts[0];
  4779. $reading =~ tr/://d;
  4780. $value = $parts[1];
  4781. if($event =~ m/T: / && $event =~ m/H: /)
  4782. {
  4783. return undef; #dewpoint workaround - no logging
  4784. }
  4785. elsif($event =~ m/symbol/ || $event =~ m/message/)
  4786. {
  4787. $unit = ''; #symbols & text
  4788. }
  4789. elsif($event =~ m/trend/)
  4790. {
  4791. $unit = ''; #trends
  4792. }
  4793. elsif($event =~ m/date/ || $event =~ m/sunrise/ || $event =~ m/sunset/)
  4794. {
  4795. $unit = ''; #dates
  4796. }
  4797. elsif($event =~ m/temp/ || $event =~ m/dewpoint/)
  4798. {
  4799. $unit = "˚C";
  4800. }
  4801. elsif($event =~ m/humidity/)
  4802. {
  4803. $unit = '%';
  4804. }
  4805. elsif($event =~ m/pressure/)
  4806. {
  4807. $unit = 'mbar';
  4808. }
  4809. elsif($event =~ m/co2/)
  4810. {
  4811. $unit = 'ppm';
  4812. }
  4813. elsif($event =~ m/noise/)
  4814. {
  4815. $unit = 'dB';
  4816. }
  4817. elsif($event =~ m/rain/)
  4818. {
  4819. $unit = 'mm';
  4820. }
  4821. elsif($event =~ m/angle/ || $event =~ m/direction/)
  4822. {
  4823. $unit = "deg";
  4824. }
  4825. elsif($event =~ m/strength/ || $event =~ m/gust/)
  4826. {
  4827. $unit = 'km/h';
  4828. }
  4829. elsif($event =~ m/boilero/)
  4830. {
  4831. $unit = 'sec';
  4832. }
  4833. elsif($event =~ m/percent/)
  4834. {
  4835. $unit = '%';
  4836. }
  4837. elsif($event =~ m/sun/)
  4838. {
  4839. $unit = 'h';
  4840. }
  4841. elsif($event =~ m/air_/)
  4842. {
  4843. $unit = "ug/m3";
  4844. }
  4845. else
  4846. {
  4847. $value = $parts[1];
  4848. $value = $value." ".$parts[2] if(defined($parts[2]));
  4849. }
  4850. return ($reading, $value, $unit);
  4851. }
  4852. sub netatmo_weatherIcon()
  4853. {
  4854. my $svgheader = '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">';
  4855. my $svgcontent = '<path id="smallcloud" opacity="1.0" fill="#000000" d="M162.8,46.7c-7.4-5.8-16.5-9.5-26.5-9.5C128.8,14.8,108.1,0,84.3,0h-1.6';
  4856. $svgcontent .= ' c-1.1,0-2.2,0-3.3,0.1C71.4,0.7,62,3.1,58,6.9V6.6c0,0.1-0.2,0.2-0.3,0.3c-12.2,6.9-21.2,18-25.4,32.3C13.2,45.1-0.1,62.1-0.1,82.8';
  4857. $svgcontent .= ' c0,25.5,20.7,46.2,46.2,46.2h88.6c25.5,0,45.6-20.8,45.6-46.2c0-5.3-0.5-10.6-2.7-15.9C173.5,56.2,171.8,52.7,162.8,46.7z';
  4858. $svgcontent .= ' M134.7,104H46.1c-11.7,0-20.7-9.6-20.7-21.2c0-10.6,8-19.6,18.6-20.7l8-1.1c2.1,0,2.7-0.5,2.7-2.7l1.1-7.4';
  4859. $svgcontent .= ' c1.6-14.8,13.8-26,28.6-26c15.4,0,27.6,11.1,29.2,26l1.1,8.5c0.5,1.6,1.6,2.6,3.2,2.6h17c11.1,0,20.7,9.6,20.7,20.7';
  4860. $svgcontent .= ' C155.3,94.4,145.8,104,134.7,104z"/>';
  4861. my $svgfooter = '</svg>';
  4862. return $svgheader . $svgcontent . $svgfooter;
  4863. }
  4864. 1;
  4865. =pod
  4866. =item device
  4867. =item summary Netatmo weather stations, thermostats and cameras connected via the official API
  4868. =begin html
  4869. <a name="netatmo"></a>
  4870. <h3>netatmo</h3>
  4871. <ul>
  4872. FHEM module for netatmo weather stations, thermostats and cameras.<br><br>
  4873. Notes:
  4874. <ul>
  4875. <li>JSON has to be installed on the FHEM host.</li>
  4876. <li>You need to create an app <u><a href="https://dev.netatmo.com/dev/createanapp">here</a></u> to get your <i>client_id / client_secret</i>.<br />Request the full access scope including cameras and thermostats.</li>
  4877. </ul><br>
  4878. <a name="netatmo_Define"></a>
  4879. <b>Define</b>
  4880. <ul>
  4881. <code>define &lt;name&gt; netatmo [ACCOUNT] &lt;username&gt; &lt;password&gt; &lt;client_id&gt; &lt;client_secret&gt;</code><br>
  4882. <code>define &lt;name&gt; netatmo &lt;device&gt;</code><br>
  4883. <br>
  4884. Defines a netatmo device.<br><br>
  4885. If a netatmo device of the account type is created all fhem devices for the netatmo devices are automaticaly created
  4886. (if autocreate is not disabled).
  4887. <br>
  4888. Examples:
  4889. <ul>
  4890. <code>define netatmo netatmo ACCOUNT abc@test.com myPassword 2134123412399119d4123134 AkqcOIHqrasfdaLKcYgZasd987123asd</code><br>
  4891. <code>define netatmo netatmo 2f:13:2b:93:12:31</code><br>
  4892. <code>define netatmo netatmo MODULE 2f:13:2b:93:12:31 f1:32:b9:31:23:11</code><br>
  4893. <code>define netatmo netatmo HOME 1234567890abcdef12345678</code><br>
  4894. <code>define netatmo netatmo CAMERA 1234567890abcdef12345678 70:ee:12:34:56:78</code><br>
  4895. <code>define netatmo netatmo PERSON 1234567890abcdef12345678 01234567-89ab-cdef-0123-456789abcdef</code><br>
  4896. </ul>
  4897. </ul><br>
  4898. <a name="netatmo_Webhook"></a>
  4899. <b>Webhook</b><br>
  4900. <ul>
  4901. <code>define netatmo netatmo WEBHOOK</code><br><br>
  4902. Set your URL in attribute webhookURL, events from cameras will be received insantly
  4903. </ul><br>
  4904. <a name="netatmo_Readings"></a>
  4905. <b>Readings</b>
  4906. <ul>
  4907. </ul><br>
  4908. <a name="netatmo_Set"></a>
  4909. <b>Set</b>
  4910. <ul>
  4911. <li>autocreate<br>
  4912. Create fhem devices for all netatmo weather devices.</li>
  4913. <li>autocreate_homes<br>
  4914. Create fhem devices for all netatmo homes, cameras and persons.</li>
  4915. <li>autocreate_thermostats<br>
  4916. Create fhem devices for all netatmo relays and thermostats.</li>
  4917. <li>autocreate_homecoachs<br>
  4918. Create fhem devices for all netatmo homecoachs.</li>
  4919. </ul><br>
  4920. <a name="netatmo_Get"></a>
  4921. <b>Get</b><br />
  4922. ACCOUNT
  4923. <ul>
  4924. <li>devices<br>
  4925. list the netatmo weather devices for this account</li>
  4926. <li>home<br>
  4927. list the netatmo home devices for this account</li>
  4928. <li>update<br>
  4929. trigger a global update for dashboard data</li>
  4930. <li>public [&lt;address&gt;] &lt;args&gt;<br>
  4931. no arguments -> get all public stations in a radius of 0.025&deg; around global fhem latitude/longitude<br>
  4932. &lt;rad&gt; -> get all public stations in a radius of &lt;rad&gt;&deg; around global fhem latitude/longitude<br>
  4933. &lt;lat&gt; &lt;lon&gt; [&lt;rad&gt;] -> get all public stations in a radius of 0.025&deg; or &lt;rad&gt;&deg; around &lt;lat&gt;/&lt;lon&gt;<br>
  4934. &lt;lat1&gt; &lt;lon1&gt; &lt;lat2&gt; &lt;lon2&gt; -> get all public stations in the area of &lt;lat1&gt; &lt;lon2&gt; &lt;lat2&gt; &lt;lon2&gt;<br>
  4935. if &lt;address&gt; is given then list stations in the area of this address. can be given as 5 digit german postal code or a: followed by a textual address. all spaces have to be replaced by a +.<br>
  4936. &lt;lat&gt; &lt;lon&gt; values can also be entered as a single coordinates parameter &lt;lat&gt;,&lt;lon&gt;<br></li>
  4937. </ul><br>
  4938. DEVICE/MODULE
  4939. <ul>
  4940. <li>update<br>
  4941. update the device readings</li>
  4942. <li>updateAll<br>
  4943. update the device readings after deleting all current readings</li>
  4944. </ul><br>
  4945. HOME
  4946. <ul>
  4947. <li>update<br>
  4948. update the home events and all camera and person readings</li>
  4949. </ul><br>
  4950. CAMERA
  4951. <ul>
  4952. <li>ping<br>
  4953. ping the camera and get the local command url</li>
  4954. <li>live/_local<br>
  4955. get the playlist for live video (internet or local network)</li>
  4956. <li>video/_local &lt;video_id&gt;<br>
  4957. get the playlist for a video id (internet or local network)</li>
  4958. </ul><br>
  4959. PRESENCE
  4960. <ul>
  4961. <li>config<br>
  4962. read the camera config</li>
  4963. <li>timelapse<br>
  4964. get the link for a timelapse video (local network)</li>
  4965. </ul><br>
  4966. PERSON
  4967. <ul>
  4968. <li>update<br>
  4969. n/a</li>
  4970. </ul><br>
  4971. <a name="netatmo_Attr"></a>
  4972. <b>Attributes</b>
  4973. <ul>
  4974. <li>interval<br>
  4975. the interval in seconds used to check for new values.</li>
  4976. <li>disable<br>
  4977. 1 -> stop polling</li>
  4978. <li>addresslimit<br>
  4979. maximum number of addresses to resolve in public station searches (ACCOUNT - default: 10)</li>
  4980. <li>setpoint_duration<br>
  4981. setpoint duration in minutes (THERMOSTAT - default: 60)</li>
  4982. <li>videoquality<br>
  4983. video quality for playlists (HOME - default: medium)</li>
  4984. <li>webhookURL<br>
  4985. webhook URL - can include basic auth and ports: http://user:pass@your.url:8080/fhem/netatmo (WEBHOOK)</li>
  4986. <li>webhookPoll<br>
  4987. poll home after event from webhook (WEBHOOK - default: 0)</li>
  4988. <li>ignored_device_ids<br>
  4989. ids of devices/persons ignored on autocrate (ACCOUNT - comma separated)</li>
  4990. </ul>
  4991. </ul>
  4992. =end html
  4993. =cut