38_netatmo.pm 233 KB

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