38_netatmo.pm 232 KB

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