dkozinn

wfpiconsole.kv

Feb 6th, 2022
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 39.76 KB | None | 0 0
  1. ## WeatherFlow PiConsole: Raspberry Pi Python console for WeatherFlow Tempest
  2. ## and Smart Home Weather stations.
  3. ## Copyright (C) 2018-2021 Peter Davis
  4.  
  5. ## This program is free software: you can redistribute it and/or modify it under
  6. ## the terms of the GNU General Public License as published by the Free Software
  7. ## Foundation, either version 3 of the License, or (at your option) any later
  8. ## version.
  9.  
  10. ## This program is distributed in the hope that it will be useful, but WITHOUT
  11. ## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  12. ## FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  13. ## details.
  14.  
  15. ## You should have received a copy of the GNU General Public License along with
  16. ## this program. If not, see <http://www.gnu.org/licenses/>.
  17.  
  18. ## Import required modules
  19. #:import NoTransition kivy.uix.screenmanager.NoTransition
  20. #:import Factory kivy.factory.Factory
  21. #:import utils kivy.utils
  22.  
  23. ## Include required kv lang files
  24. #:include kvlang/displayElements.kv
  25. #:include kvlang/displayFields.kv
  26. #:include kvlang/panelLayout.kv
  27.  
  28. ## =============================================================================
  29. ## SCREEN MANAGER
  30. ## =============================================================================
  31. ScreenManager:
  32. transition: NoTransition()
  33. CurrentConditions:
  34. id: CurrentConditions
  35. name: 'CurrentConditions'
  36.  
  37. ## =============================================================================
  38. ## CURRRENT CONDITIONS SCREEN
  39. ## =============================================================================
  40. <CurrentConditions>:
  41. name: 'CurrentConditions'
  42. orientation: 'vertical'
  43. canvas.before:
  44. Color:
  45. rgba: utils.rgba('000000ff')
  46. Rectangle:
  47. pos: self.pos
  48. size: self.size
  49. BoxLayout:
  50. padding: ['2dp', '2dp', '2dp', '0dp']
  51. spacing: '3dp'
  52. orientation: 'vertical'
  53. BoxLayout:
  54. spacing: '4dp'
  55. orientation: 'vertical'
  56. BoxLayout:
  57. spacing: '5dp'
  58. BoxLayout:
  59. id: PanelOne
  60. BoxLayout:
  61. id: PanelTwo
  62. BoxLayout:
  63. id: PanelThree
  64. BoxLayout:
  65. spacing: '5dp'
  66. BoxLayout:
  67. id: PanelFour
  68. BoxLayout:
  69. id: PanelFive
  70. BoxLayout:
  71. id: PanelSix
  72. BoxLayout:
  73. spacing: '4dp'
  74. size_hint: (1,31/444)
  75. BoxLayout:
  76. id: ButtonOne
  77. BoxLayout:
  78. id: ButtonTwo
  79. BoxLayout:
  80. id: ButtonThree
  81. BoxLayout:
  82. id: ButtonFour
  83. BoxLayout:
  84. id: ButtonFive
  85. BoxLayout:
  86. id: ButtonSix
  87. BoxLayout:
  88. id: GraphsButton
  89. BoxLayout:
  90. PanelButton:
  91. id: 'mainPanelButton'
  92. text: 'Menu'
  93. on_release: Factory.mainMenu().open()
  94.  
  95. ## =============================================================================
  96. ## WEATHERFLOW FORECAST PANEL AND BUTTON
  97. ## =============================================================================
  98. <ForecastPanel>:
  99.  
  100. ## Panel background and title
  101. PanelBackground:
  102. _panelTitle: 'Forecast'
  103.  
  104. ## Forecast Conditions
  105. TitleField:
  106. text: 'Conditions'
  107. pos_hint: {'x': 18/262, 'y': 166/202}
  108. size_hint_x: 77/262
  109. Image:
  110. source: 'icons/weather/' + app.MetData['Icon'] + app.scaleSuffix + '.png'
  111. pos_hint: {'x': 18/262, 'y': 83/202}
  112. size_hint: (77/262, 80/202)
  113. keep_ratio: 0
  114. allow_stretch: 1
  115. SmallMultilineField:
  116. text: app.MetData['Conditions']
  117. pos_hint: {'x': 110/262, 'y': 80/202}
  118. size_hint: (132/262, 62/202)
  119. halign: 'left'
  120. line_height: 1.2
  121.  
  122. ## Forecast status
  123. BoldMultilineField:
  124. text: app.MetData['Status']
  125. pos_hint: {'x': 0/262, 'y': 97/202}
  126. size_hint: (262/262, 35/202)
  127.  
  128. ## Forecast windspeed
  129. TitleField:
  130. text: 'Wind Speed'
  131. pos_hint: {'x': 132/262, 'y': 166/202}
  132. size_hint_x: 110/262
  133. MediumField:
  134. text: app.MetData['WindSpd'][0] + ' ' + app.MetData['WindSpd'][1] + ' ' + app.MetData['WindDir'][0]
  135. pos_hint: {'x': 132/262, 'y': 145/202}
  136. size_hint_x: (110/262)
  137.  
  138. ## Forecast temperature
  139. TitleField:
  140. text: 'Temperature'
  141. pos_hint: {'x': 5/262, 'y': 65/202}
  142. size_hint_x: (125/262)
  143. MediumField:
  144. text: app.MetData['Temp'][0] + app.MetData['Temp'][1]
  145. pos_hint: {'x': 5/262, 'y': 43/202}
  146. size_hint_x: (125/262)
  147. SmallField:
  148. pos_hint: {'x': 5/262, 'y': 25/202}
  149. size_hint_x: (125/262)
  150. text:
  151. '[color=00a4b4ff][font=fonts/Inter-Bold.ttf]L: [/font]' + app.MetData['lowTemp'][0] + app.MetData['lowTemp'][1] + '[/color]' + ' ' + \
  152. '[color=f05e40ff]' + '[font=fonts/Inter-Bold.ttf]H: [/font]' + app.MetData['highTemp'][0] + app.MetData['highTemp'][1] + '[/color]'
  153.  
  154. ## Forecast rain/snowfall
  155. TitleField:
  156. text: app.MetData['PrecipType'].title() + 'fall'
  157. pos_hint: {'x': 152/262, 'y': 65/202}
  158. size_hint_x: (100/262)
  159. MediumField:
  160. text: app.MetData['PrecipPercnt'][0] + app.MetData['PrecipPercnt'][1]
  161. pos_hint: {'x': 152/262, 'y': 43/202}
  162. size_hint_x: (100/262)
  163. SmallField:
  164. text: '[color=ffffffff]Daily[/color]: ' + app.MetData['PrecipDay'][0] + app.MetData['PrecipDay'][1]
  165. pos_hint: {'x': 152/262, 'y': 25/202}
  166. size_hint_x: (100/262)
  167.  
  168. ## Forecast valid time
  169. SmallField:
  170. text: 'Forecast for ' + app.MetData['Valid']
  171. pos_hint: {'x': 5/262, 'y': 2/202}
  172. size_hint: (252/262, 17/202)
  173. text_size: self.size
  174. halign: 'right'
  175.  
  176. <ForecastButton>:
  177. PanelButton:
  178. id: 'ForecastButton'
  179. text: 'Forecast'
  180. on_release: app.CurrentConditions.SwitchPanel(self)
  181.  
  182. ## =============================================================================
  183. ## SAGER FORECAST PANEL AND BUTTON
  184. ## =============================================================================
  185. <SagerPanel>:
  186.  
  187. ## Panel background and title
  188. PanelBackground:
  189. _panelTitle: 'Sager Forecast'
  190.  
  191. ## Sager Weathercaster forecast
  192. MediumMultilineField:
  193. text: app.Sager['Forecast']
  194. pos_hint: {'x': 5/262, 'y': 21/202}
  195. size_hint: (252/262, 157/202)
  196.  
  197. ## Forecast issue time
  198. SmallField:
  199. text: 'Forecast issued: ' + app.Sager['Issued']
  200. pos_hint: {'x': 5/262, 'y': 2/202}
  201. size_hint: (252/262, 17/202)
  202. text_size: self.size
  203. halign: 'right'
  204.  
  205. <SagerButton>:
  206. PanelButton:
  207. id: 'SagerButton'
  208. text: 'Sager'
  209. on_release: app.CurrentConditions.SwitchPanel(self)
  210.  
  211. ## =============================================================================
  212. ## TEMPERATURE WIDGET
  213. ## =============================================================================
  214. <TemperaturePanel>:
  215.  
  216. ## Panel background and title
  217. PanelBackground:
  218. _panelTitle: 'Temperature'
  219.  
  220. ## Indoor temperature
  221. TitleField:
  222. text: 'Indoor'
  223. pos_hint: {'x': 0/262, 'y': 166/202}
  224. size_hint_x: (131/262)
  225. opacity: 1 if app.IndoorTemp == '1' else 0
  226. LargeField:
  227. text: app.Obs['inTemp'][0] + app.Obs['inTemp'][1]
  228. pos_hint: {'x': 0/262, 'y': 132/202}
  229. size_hint_x: (131/262)
  230. opacity: 1 if app.IndoorTemp == '1' else 0
  231.  
  232. ## Indoor temperature minimum
  233. MediumField:
  234. text: '[size=' + str(int(self.font_size*0.88)) + '][color=00a4b4ff]' + app.Obs['inTempMin'][0] + '[size=' + str(int(self.font_size*0.83)) + ']' + app.Obs['inTempMin'][1] + '[/color][/size][/size]'
  235. pos_hint: {'x': 0/262, 'y': 106/202}
  236. size_hint_x: (65.5/262)
  237. opacity: 1 if app.IndoorTemp == '1' else 0
  238. SmallField:
  239. text: app.Obs['inTempMin'][2]
  240. pos_hint: {'x': 0/262, 'y': 86/202}
  241. size_hint_x: (65.5/262)
  242. opacity: 1 if app.IndoorTemp == '1' else 0
  243.  
  244. ## Indoor temperature maximum
  245. MediumField:
  246. text: '[size=' + str(int(self.font_size*0.88)) + '][color=f05e40ff]' + app.Obs['inTempMax'][0] + '[size=' + str(int(self.font_size*0.83)) + ']' + app.Obs['inTempMax'][1] + '[/color][/size][/size]'
  247. pos_hint: {'x': 65.5/262, 'y': 106/202}
  248. size_hint_x: (65.5/262)
  249. opacity: 1 if app.IndoorTemp == '1' else 0
  250. SmallField:
  251. text: app.Obs['inTempMax'][2]
  252. pos_hint: {'x': 65.5/262, 'y': 86/202}
  253. size_hint_x: (65.5/262)
  254. opacity: 1 if app.IndoorTemp == '1' else 0
  255.  
  256. ## Outdoor temperature
  257. TitleField:
  258. text: 'Outdoor'
  259. pos_hint: {'x': 131/262 if app.IndoorTemp == '1' else 0/262, 'y': 166/202}
  260. size_hint_x: (131/262 if app.IndoorTemp == '1' else 262/262)
  261. LargeField:
  262. text: app.Obs['outTemp'][0] + app.Obs['outTemp'][1]
  263. pos_hint: {'x': 131/262 if app.IndoorTemp == '1' else 65.5/262, 'y': 132/202}
  264. size_hint_x: (131/262)
  265.  
  266. ## Outdoor temperature minimum
  267. MediumField:
  268. text: '[size=' + str(int(self.font_size*0.88)) + '][color=00a4b4ff]' + app.Obs['outTempMin'][0] + '[size=' + str(int(self.font_size*0.83)) + ']' + app.Obs['outTempMin'][1] + '[/color][/size][/size]'
  269. pos_hint: {'x': 131/262 if app.IndoorTemp == '1' else 65.5/262, 'y': 106/202}
  270. size_hint_x: (65.5/262)
  271. SmallField:
  272. text: app.Obs['outTempMin'][2]
  273. pos_hint: {'x': 131/262 if app.IndoorTemp == '1' else 65.5/262, 'y': 86/202}
  274. size_hint_x: (65.5/262)
  275.  
  276. ## Outdoor temperature maximum
  277. MediumField:
  278. text: '[size=' + str(int(self.font_size*0.88)) + '][color=f05e40ff]' + app.Obs['outTempMax'][0] + '[size=' + str(int(self.font_size*0.83)) + ']' + app.Obs['outTempMax'][1] + '[/color][/size][/size]'
  279. pos_hint: {'x': 196.5/262 if app.IndoorTemp == '1' else 131/262, 'y': 106/202}
  280. size_hint_x: (65.5/262)
  281. SmallField:
  282. text: app.Obs['outTempMax'][2]
  283. pos_hint: {'x': 196.5/262 if app.IndoorTemp == '1' else 131/262, 'y': 86/202}
  284. size_hint_x: (65.5/262)
  285.  
  286. ## 'Feels like' temperature
  287. TitleField:
  288. text: 'Feels Like'
  289. pos_hint: {'x': 0/262, 'y': 65/202}
  290. size_hint_x: (87.330/262)
  291. MediumField:
  292. text: app.Obs['FeelsLike'][0] + app.Obs['FeelsLike'][1]
  293. pos_hint: {'x': 0/262, 'y': 43/202}
  294. size_hint_x: (87.330/262)
  295.  
  296. ## Outdoor humidity
  297. TitleField:
  298. text: 'Humidity'
  299. pos_hint: {'x': 87.330/262, 'y': 65/202}
  300. size_hint_x: (87.330/262)
  301. MediumField:
  302. text: app.Obs['Humidity'][0] + app.Obs['Humidity'][1]
  303. pos_hint: {'x': 87.330/262, 'y': 43/202}
  304. size_hint_x: (87.330/262)
  305.  
  306. ## Dew point
  307. TitleField:
  308. text: 'Dew Point'
  309. pos_hint: {'x': 174.660/262, 'y': 65/202}
  310. size_hint_x: (87.330/262)
  311. MediumField:
  312. text: app.Obs['DewPoint'][0] + app.Obs['DewPoint'][1]
  313. pos_hint: {'x': 174.660/262, 'y': 43/202}
  314. size_hint_x: (87.330/262)
  315.  
  316. ## 'Feels like' icon and text
  317. Image:
  318. source: 'atlas://atlas/feelsLike/' + root.feelsLikeIcon + app.scaleSuffix
  319. pos_hint: {'x': 7/262, 'y': 7/202}
  320. size_hint: (36/262, 31/202)
  321. keep_ratio: 0
  322. allow_stretch: 1
  323. SmallField:
  324. text: app.Obs['FeelsLike'][2]
  325. pos_hint: {'x': 49/262, 'y': 10/202}
  326. size_hint: (200/262, 17/202)
  327. text_size: self.size
  328. halign: 'left'
  329.  
  330. <TemperatureButton>:
  331. PanelButton:
  332. id: 'TemperatureButton'
  333. text: 'Temperature'
  334. on_release: app.CurrentConditions.SwitchPanel(self)
  335.  
  336. ## =============================================================================
  337. ## WIND SPEED AND DIRECTION WIDGET
  338. ## =============================================================================
  339. <WindSpeedPanel>:
  340.  
  341. ## Panel background and title
  342. PanelBackground:
  343. _panelTitle: 'Wind Speed [size=' + str(int(self.ids.Title.font_size*0.8)) + ']|[/size] Direction'
  344.  
  345. ## Rapid Wind wind speed and direction
  346. windRose:
  347. Image:
  348. source: 'icons/windrose/icons' + app.scaleSuffix + '/' + str(round(root.rapidWindDir)) + '.png'
  349. pos_hint: {'x': 0, 'y': 0}
  350. size_hint: (1, 1)
  351. keep_ratio: 0
  352. allow_stretch: 1
  353.  
  354. ## Current average wind speed
  355. LargeField:
  356. text: app.Obs['WindSpd'][0]
  357. pos_hint: {'x': 3/262, 'y': 95/202}
  358. size_hint_x: (60/262)
  359. MediumField:
  360. text: app.Obs['WindSpd'][1]
  361. pos_hint: {'x': 3/262, 'y': 78/202}
  362. size_hint_x: (60/262)
  363.  
  364. ## Daily averaged wind speed
  365. SmallField:
  366. text: 'Avg [color=ff8837ff]' + app.Obs['AvgWind'][0] + '[/color] ' + app.Obs['AvgWind'][1]
  367. pos_hint: {'x': 3/262, 'y': 164/202}
  368. size_hint_x: (101/262)
  369. SmallField:
  370. text: 'Wind'
  371. pos_hint: {'x': 3/262, 'y': 147/202}
  372. size_hint_x: (101/262)
  373.  
  374. ## Current wind gust
  375. LargeField:
  376. text: app.Obs['WindGust'][0]
  377. pos_hint: {'x': 201/262, 'y': 95/202}
  378. size_hint_x: (60/262)
  379. MediumField:
  380. text: app.Obs['WindGust'][1]
  381. pos_hint: {'x': 201/262, 'y': 78/202}
  382. size_hint_x: (60/262)
  383.  
  384. ## Maximum wind gust
  385. SmallField:
  386. text: 'Max [color=ff8837ff]' + app.Obs['MaxGust'][0] + '[/color] ' + app.Obs['MaxGust'][1]
  387. pos_hint: {'x': 159/262, 'y': 164/202}
  388. size_hint_x: (101/262)
  389. SmallField:
  390. text: 'Gust'
  391. pos_hint: {'x': 159/262, 'y': 147/202}
  392. size_hint_x: (101/262)
  393.  
  394. ## Current Beaufort scale text and icon
  395. Image:
  396. source: 'atlas://atlas/windSpd/' + root.windSpdIcon + app.scaleSuffix
  397. pos_hint: {'x': 18/262, 'y': 28/202}
  398. size_hint: (30/262, 30/202)
  399. keep_ratio: 0
  400. allow_stretch: 1
  401. SmallField:
  402. text: app.Obs['WindSpd'][4]
  403. pos_hint: {'x': 6/262, 'y': 6/202}
  404. size_hint: (126/262, 17/202)
  405. text_size: self.size
  406. halign: 'left'
  407.  
  408. ## Current average wind direction
  409. Image:
  410. source: 'atlas://atlas/windDir/' + root.windDirIcon + app.scaleSuffix
  411. pos_hint: {'x': 216/262, 'y': 28/202}
  412. size_hint: (30/262, 30/202)
  413. keep_ratio: 0
  414. allow_stretch: 1
  415. SmallField:
  416. text: 'Direction: [color=9aba2fff]' + app.Obs['WindDir'][0] + app.Obs['WindDir'][1] + '[/color]'
  417. pos_hint: {'x': 119/262, 'y': 6/202}
  418. size_hint: (135/262, 17/202)
  419. text_size: self.size
  420. halign: 'right'
  421.  
  422. ## Rapid wind direction in degrees
  423. MediumField:
  424. text: app.Obs['rapidDir'][0] + app.Obs['rapidDir'][1]
  425. pos_hint: {'x': 102/262, 'y': 116/202}
  426. size_hint_x: (60/262)
  427.  
  428. ## Rapid wind speed
  429. MediumField:
  430. text: app.Obs['rapidSpd'][0] + ' [size=' + str(int(self.font_size*0.8)) + ']' + app.Obs['rapidSpd'][1] + '[/size]'
  431. pos_hint: {'x': 92/262, 'y': 89/202}
  432. size_hint_x: (80/262)
  433.  
  434. ## Rapid wind direction text
  435. SmallField:
  436. text: app.Obs['rapidDir'][3]
  437. pos_hint: {'x': 92/262, 'y': 65.5/202}
  438. size_hint_x: (80/262)
  439.  
  440. <WindSpeedButton>:
  441. PanelButton:
  442. id: 'WindSpeedButton'
  443. text: 'Wind Speed'
  444. on_release: app.CurrentConditions.SwitchPanel(self)
  445.  
  446. ## =============================================================================
  447. ## SUNRISE/SUNSET WIDGET
  448. ## =============================================================================
  449. <SunriseSunsetPanel>:
  450.  
  451. ## Panel background and title
  452. PanelBackground:
  453. _panelTitle: 'Solar [size=' + str(int(self.ids.Title.font_size*0.8)) + ']|[/size] UV Index'
  454.  
  455. ## Night bar
  456. BoxLayout:
  457. pos_hint: {'x': 13/262, 'y': 96/202}
  458. size_hint: [236/262, 10/202]
  459. canvas.after:
  460. Color:
  461. rgba: [100/255,100/255,100/255,1]
  462. Rectangle:
  463. pos: self.pos
  464. size: self.size
  465.  
  466. ## Dawn/Dusk bar
  467. BoxLayout:
  468. pos_hint: {'x': (13+app.Astro['Dawn'][2]*236)/262, 'y': 96/202}
  469. size_hint: [(app.Astro['Dusk'][2]*236)/262, 10/202]
  470. canvas.after:
  471. Color:
  472. rgba: [29/255,74/255,87/255,1]
  473. Rectangle:
  474. pos: self.pos
  475. size: self.size
  476.  
  477. ## Sunrise/sunset bar
  478. BoxLayout:
  479. pos_hint: {'x': (13+app.Astro['Sunrise'][2]*236)/262, 'y': 96/202}
  480. size_hint: [(app.Astro['Sunset'][2]*236)/262, 10/202]
  481. canvas.after:
  482. Color:
  483. rgba: [0/255,113/255,123/255,1]
  484. Rectangle:
  485. pos: self.pos
  486. size: self.size
  487.  
  488. ## Sun bar and icon position
  489. BoxLayout:
  490. pos_hint: {'x': 13/262, 'y': 94/202}
  491. size_hint: [236/262, 14/202]
  492. canvas.after:
  493. Color:
  494. rgba: [230/255,75/255,36/255,app.Astro['sunIcon'][1]]
  495. Line:
  496. width: 1.2 * app.scaleFactor
  497. rectangle: [self.x + app.Astro['sunIcon'][2]*self.width, self.y, 1, self.height]
  498. Image:
  499. source: 'atlas://atlas/sunIcon/' + app.Astro['sunIcon'][0] + app.scaleSuffix
  500. pos_hint: {'x': (4+app.Astro['sunIcon'][2]*236)/262, 'y': 92/202}
  501. size_hint: (18/262, 18/202)
  502. keep_ratio: 0
  503. allow_stretch: 1
  504.  
  505. ## Solar radiation
  506. TitleField:
  507. text: 'Radiation'
  508. pos_hint: {'x': 3/262, 'y': 166/202}
  509. size_hint_x: (105/262)
  510. MediumField:
  511. text: app.Obs['Radiation'][0] + app.Obs['Radiation'][1]
  512. pos_hint: {'x': 3/262, 'y': 138/202}
  513. size_hint_x: (105/262)
  514.  
  515. ## UV Index
  516. TitleField:
  517. text: 'UV Index'
  518. pos_hint: {'x': 125/262, 'y': 166/202}
  519. size_hint_x: (120/262)
  520. MediumField:
  521. text: app.Obs['UVIndex'][0]
  522. pos_hint: {'x': 125/262, 'y': 138/202}
  523. size_hint_x: (40/262)
  524. TitleField:
  525. text: app.Obs['UVIndex'][2]
  526. pos_hint: {'x': 170/262, 'y': 140/202}
  527. size_hint_x: (75/262)
  528. color: utils.rgba('#000000ff')
  529. canvas.before:
  530. Color:
  531. rgba: utils.rgba(root.uvBackground) if root.uvBackground != '-' else utils.rgba('000000ff')
  532. RoundedRectangle:
  533. pos: [self.pos[0],self.pos[1]-4*app.scaleFactor]
  534. size: (self.width,self.height+8*app.scaleFactor)
  535. radius: [5,]
  536.  
  537. ## Sunrise time
  538. TitleField:
  539. text: 'Sunrise'
  540. pos_hint: {'x': 13/262, 'y': 113/202}
  541. size_hint: (55/262, 17/202)
  542. text_size: self.size
  543. halign: 'left'
  544. SmallField:
  545. text: app.Astro['Sunrise'][1]
  546. pos_hint: {'x': 13/262, 'y': 73/202}
  547. size_hint: (80/262, 17/202)
  548. text_size: self.size
  549. halign: 'left'
  550.  
  551. ## Sunset time
  552. TitleField:
  553. text: 'Sunset'
  554. pos_hint: {'x': 194/262, 'y': 113/202}
  555. size_hint: (55/262, 17/202)
  556. text_size: self.size
  557. halign: 'right'
  558. SmallField:
  559. text: app.Astro['Sunset'][1]
  560. pos_hint: {'x': 169/262, 'y': 73/202}
  561. size_hint: (80/262, 17/202)
  562. text_size: self.size
  563. halign: 'right'
  564.  
  565. ## Time remaining until sunrise or sunset
  566. TitleField:
  567. text: app.Astro['sunEvent'][3]
  568. pos_hint: {'x': 24/262, 'y': 50/202}
  569. size_hint_x: (80/262)
  570. MediumField:
  571. text: app.Astro['sunEvent'][1]
  572. pos_hint: {'x': 10/262, 'y': 27/202}
  573. size_hint_x: (30/262)
  574. TitleField:
  575. text: 'hrs'
  576. font_size: dp(10.9*app.scaleFactor)
  577. pos_hint: {'x': 40/262, 'y': 25/202}
  578. size_hint_x: (20/262)
  579. color: utils.rgba('ffffffff')
  580. MediumField:
  581. text: app.Astro['sunEvent'][2]
  582. pos_hint: {'x': 60/262, 'y': 27/202}
  583. size_hint_x: (30/262)
  584. TitleField:
  585. text: 'mins'
  586. font_size: dp(10.9*app.scaleFactor)
  587. pos_hint: {'x': 90/262, 'y': 25/202}
  588. size_hint_x: (28/262)
  589. color: utils.rgba('ffffffff')
  590. SmallField:
  591. text: 'Till ' + app.Astro['sunEvent'][0]
  592. pos_hint: {'x': 24/262, 'y': 5/202}
  593. size_hint_x: (80/262)
  594.  
  595. ## Peak Sun Hours
  596. TitleField:
  597. text: 'Peak Sun Hours'
  598. pos_hint: {'x': 132/262, 'y': 50/202}
  599. size_hint_x: (120/262)
  600. MediumField:
  601. text: app.Obs['peakSun'][0]
  602. pos_hint: {'x': 152/262, 'y': 26/202}
  603. size_hint_x: (80/262)
  604. SmallField:
  605. text: 'Solar: ' + app.Obs['peakSun'][4]
  606. pos_hint: {'x': 132/262, 'y': 5/202}
  607. size_hint_x: (120/262)
  608.  
  609. <SunriseSunsetButton>:
  610. PanelButton:
  611. id: 'SunriseButton'
  612. text: 'Solar'
  613. on_release: app.CurrentConditions.SwitchPanel(self)
  614.  
  615. ## =============================================================================
  616. ## MOON PHASE WIDGET
  617. ## =============================================================================
  618. <MoonPhasePanel>
  619.  
  620. ## Panel background, title, and MoonRose
  621. PanelBackground:
  622. _panelTitle: 'Moon Phase'
  623. moonRose:
  624.  
  625. ## Moonrise time
  626. TitleField:
  627. text: 'Moonrise'
  628. pos_hint: {'x': 5/262, 'y': 23/202}
  629. size_hint_x: (80/262)
  630. SmallField:
  631. text: app.Astro['Moonrise'][1]
  632. pos_hint: {'x': 5/262, 'y': 5/202}
  633. size_hint_x: (80/262)
  634.  
  635. ## Moonset time
  636. TitleField:
  637. text: 'Moonset'
  638. pos_hint: {'x': 177/262, 'y': 23/202}
  639. size_hint_x: (80/262)
  640. SmallField:
  641. text: app.Astro['Moonset'][1]
  642. pos_hint: {'x': 177/262, 'y': 5/202}
  643. size_hint_x: (80/262)
  644.  
  645. ## Next new moon
  646. TitleField:
  647. text: 'Next'
  648. pos_hint: {'x': 6/262, 'y': 170/202}
  649. size_hint_x: (70/262)
  650. TitleField:
  651. text: '[color=00a4b4ff]New[/color] Moon'
  652. pos_hint: {'x': 6/262, 'y': 156/202}
  653. size_hint_x: (70/262)
  654. SmallField:
  655. text: app.Astro['NewMoon'][0]
  656. pos_hint: {'x': 6/262, 'y': 138/202}
  657. size_hint_x: (70/262)
  658.  
  659. ## Next full moon
  660. TitleField:
  661. text: 'Next'
  662. pos_hint: {'x': 186/262, 'y': 170/202}
  663. size_hint_x: (70/262)
  664. TitleField:
  665. text: '[color=f05e40ff]Full[/color] Moon'
  666. pos_hint: {'x': 186/262, 'y': 156/202}
  667. size_hint_x: (70/262)
  668. SmallField:
  669. text: app.Astro['FullMoon'][0]
  670. pos_hint: {'x': 186/262, 'y': 138/202}
  671. size_hint_x: (70/262)
  672.  
  673. ## Moon phase icon
  674. Image:
  675. source: 'atlas://atlas/moonIcon' + app.scaleSuffix + '/' + app.Astro['Phase'][0]
  676. pos_hint: {'x': 108/262, 'y': 93/202}
  677. size_hint: (46/262, 46/202)
  678. keep_ratio: 0
  679. allow_stretch: 1
  680.  
  681. ## Moon phase text
  682. SmallField:
  683. text: app.Astro['Phase'][1]
  684. pos_hint: {'x': 75/262, 'y': 72/202}
  685. size_hint_x: (112/262)
  686.  
  687. ## Moon illuminance text
  688. SmallField:
  689. text: app.Astro['Phase'][2] + '% Illumin.'
  690. pos_hint: {'x': 86/262, 'y': 52/202}
  691. size_hint_x: (90/262)
  692.  
  693. <MoonPhaseButton>:
  694. PanelButton:
  695. id: 'MoonPhaseButton'
  696. text: 'Moon'
  697. on_release: app.CurrentConditions.SwitchPanel(self)
  698.  
  699. ## =============================================================================
  700. ## RAINFALL WIDGET
  701. ## =============================================================================
  702. <RainfallPanel>
  703.  
  704. ## Panel background, title, and RainGauge
  705. RainfallBackground:
  706. _panelTitle: 'Rainfall'
  707. RainGauge:
  708.  
  709. ## Current rain rate and text
  710. SmallField:
  711. text: app.Obs['RainRate'][2]
  712. pos_hint: {'x': 1/262, 'y': 57/202}
  713. size_hint_x: (176/262)
  714. SmallField:
  715. text: app.Obs['RainRate'][0] + app.Obs['RainRate'][1]
  716. pos_hint: {'x': 175/262, 'y': 57/202}
  717. size_hint_x: (86/262)
  718.  
  719. ## Rain rate animation
  720. StencilView:
  721. pos_hint: {'x': 202/262, 'y': 81/202}
  722. size_hint: (33/262, 108/202)
  723. FloatLayout:
  724. width: self.parent.height*(264/108)
  725. height: self.parent.height
  726. pos: self.parent.pos
  727. Image:
  728. source: 'atlas://atlas/rainGauge/rainGauge' + app.scaleSuffix
  729. keep_ratio: 0
  730. allow_stretch: 1
  731. size_hint: (1,1)
  732. pos_hint: {'x': root.rainRatePosX, 'y': root.rainRatePosY}
  733.  
  734. ## Total daily rainfall
  735. TitleField:
  736. text: 'Today'
  737. pos_hint: {'x': 1/262, 'y': 166/202}
  738. size_hint_x: (88/262)
  739. MediumField:
  740. text: app.Obs['TodayRain'][0] + '[size=' + str(int(self.font_size*0.85)) + ']' + app.Obs['TodayRain'][1] + '[/size]'
  741. pos_hint: {'x': 1/262, 'y': 138/202}
  742. size_hint_x: (88/262)
  743.  
  744. ## Total yesterday rainfall
  745. TitleField:
  746. text: 'Yesterday'
  747. pos_hint: {'x': 89/262, 'y': 166/202}
  748. size_hint_x: (88/262)
  749. MediumField:
  750. text: app.Obs['YesterdayRain'][0] + '[size=' + str(int(self.font_size*0.85)) + ']' + app.Obs['YesterdayRain'][1] + '[/size]'
  751. pos_hint: {'x': 89/262, 'y': 138/202}
  752. size_hint_x: (88/262)
  753.  
  754. ## Total monthly rainfall
  755. TitleField:
  756. text: 'Month'
  757. pos_hint: {'x': 1/262, 'y': 111/202}
  758. size_hint_x: (88/262)
  759. MediumField:
  760. text: app.Obs['MonthRain'][0] + '[size=' + str(int(self.font_size*0.85)) + ']' + app.Obs['MonthRain'][1] + '[/size]'
  761. pos_hint: {'x': 1/262, 'y': 83/202}
  762. size_hint_x: (88/262)
  763.  
  764. ## Total yearly rainfall
  765. TitleField:
  766. text: 'Year'
  767. pos_hint: {'x': 89/262, 'y': 111/202}
  768. size_hint_x: (88/262)
  769. MediumField:
  770. text: app.Obs['YearRain'][0] + '[size=' + str(int(self.font_size*0.85)) + ']' + app.Obs['YearRain'][1] + '[/size]'
  771. pos_hint: {'x': 89/262, 'y': 83/202}
  772. size_hint_x: (88/262)
  773.  
  774. ## Time and date
  775. TimeDateField:
  776. text: app.System['Date']
  777. pos_hint: {'x': 0/262, 'y': 24/202}
  778. size_hint_x: (262/262)
  779. TimeDateField:
  780. text: app.System['Time']
  781. pos_hint: {'x': 0/262, 'y': 3/202}
  782. size_hint_x: (262/262)
  783.  
  784. <RainfallButton>:
  785. PanelButton:
  786. id: 'RainfallButton'
  787. text: 'Rainfall'
  788. on_release: app.CurrentConditions.SwitchPanel(self)
  789.  
  790. ## =============================================================================
  791. ## LIGHTNING WIDGET
  792. ## =============================================================================
  793. <LightningPanel>
  794.  
  795. ## Panel background and title
  796. PanelBackground:
  797. _panelTitle: 'Lightning'
  798.  
  799. ## Last strike time
  800. TitleField:
  801. text:
  802. 'Last Strike' if app.Obs['StrikeDeltaT'][4] == '-' else \
  803. 'Strike Detected!' if app.Obs['StrikeDeltaT'][4] < 360 else \
  804. 'Last Strike'
  805. pos_hint: {'x': 83/262, 'y': 168/202}
  806. size_hint_x: (176/262)
  807. LargeField:
  808. text: app.Obs['StrikeDeltaT'][0]
  809. pos_hint: {'x': 83/262, 'y': 139/202}
  810. size_hint_x: (80/262 if app.Obs['StrikeDeltaT'][2] == '-' else 40/262)
  811. TitleField:
  812. text: app.Obs['StrikeDeltaT'][1]
  813. font_size: dp(13*app.scaleFactor)
  814. pos_hint: {'x': 83/262, 'y': 123/202}
  815. size_hint_x: (80/262 if app.Obs['StrikeDeltaT'][2] == '-' else 40/262)
  816. LargeField:
  817. text: app.Obs['StrikeDeltaT'][2]
  818. opacity: 0 if app.Obs['StrikeDeltaT'][2] == '-' else 1
  819. pos_hint: {'x': 123/262, 'y': 139/202}
  820. size_hint_x: (40/262)
  821. TitleField:
  822. text: app.Obs['StrikeDeltaT'][3]
  823. font_size: dp(13*app.scaleFactor)
  824. opacity: 0 if app.Obs['StrikeDeltaT'][2] == '-' else 1
  825. pos_hint: {'x': 123/262, 'y': 123/202}
  826. size_hint_x: (40/262)
  827. SmallField:
  828. text: '[color=f05e40ff]ago[/color]'
  829. pos_hint: {'x': 83/262, 'y': 108/202}
  830. size_hint_x: (80/262)
  831.  
  832. ## Last strike distance
  833. LargeField:
  834. text: app.Obs['StrikeDist'][0]
  835. pos_hint: {'x': 169/262, 'y': 139/202}
  836. size_hint_x: (90/262)
  837. TitleField:
  838. text: app.Obs['StrikeDist'][1]
  839. font_size: dp(13*app.scaleFactor)
  840. pos_hint: {'x': 169/262, 'y': 123/202}
  841. size_hint_x: (90/262)
  842. SmallField:
  843. text: '[color=f05e40ff]away[/color]'
  844. pos_hint: {'x': 169/262, 'y': 108/202}
  845. size_hint_x: (90/262)
  846.  
  847. ## Last three hour lightning strikes
  848. TitleField:
  849. text: 'Strike Count'
  850. pos_hint: {'x': 6/262, 'y': 88/202}
  851. size_hint_x: (132/262)
  852. TitleField:
  853. text: '3 hours'
  854. color: utils.rgba('ffffffff')
  855. pos_hint: {'x': 6/262, 'y': 70/202}
  856. size_hint_x: (60/262)
  857. MediumField:
  858. text: app.Obs['Strikes3hr'][0]
  859. pos_hint: {'x': 6/262, 'y': 49/202}
  860. size_hint_x: (60/262)
  861.  
  862. ## Total daily lightning strikes
  863. TitleField:
  864. text: 'Today'
  865. color: utils.rgba('ffffffff')
  866. pos_hint: {'x': 78/262, 'y': 70/202}
  867. size_hint_x: (60/262)
  868. MediumField:
  869. text: app.Obs['StrikesToday'][0]
  870. pos_hint: {'x': 78/262, 'y': 49/202}
  871. size_hint_x: (60/262)
  872.  
  873. ## Total monthly lightning strikes
  874. TitleField:
  875. text: 'Month'
  876. color: utils.rgba('ffffffff')
  877. pos_hint: {'x': 6/262, 'y': 28/202}
  878. size_hint_x: (60/262)
  879. MediumField:
  880. text: app.Obs['StrikesMonth'][0]
  881. pos_hint: {'x': 6/262, 'y': 7/202}
  882. size_hint_x: (60/262)
  883.  
  884. ## Total yearly lightning strikes
  885. TitleField:
  886. text: 'Year'
  887. color: utils.rgba('ffffffff')
  888. pos_hint: {'x': 78/262, 'y': 28/202}
  889. size_hint_x: (60/262)
  890. MediumField:
  891. text: app.Obs['StrikesYear'][0]
  892. pos_hint: {'x': 78/262, 'y': 7/202}
  893. size_hint_x: (60/262)
  894.  
  895. ## Strike frequency last ten minutes
  896. TitleField:
  897. text: 'Strike Frequency'
  898. pos_hint: {'x': 143/262, 'y': 88/202}
  899. size_hint_x: (114/262)
  900. TitleField:
  901. text: '10 Minutes'
  902. color: utils.rgba('ffffffff')
  903. pos_hint: {'x': 143/262, 'y': 70/202}
  904. size_hint_x: (114/262)
  905. MediumField:
  906. text: app.Obs['StrikeFreq'][0] + app.Obs['StrikeFreq'][1]
  907. pos_hint: {'x': 143/262, 'y': 49/202}
  908. size_hint_x: (114/262)
  909.  
  910. ## Strike frequency last three hours
  911. TitleField:
  912. text: '3 hours'
  913. color: utils.rgba('ffffffff')
  914. pos_hint: {'x': 143/262, 'y': 28/202}
  915. size_hint_x: (114/262)
  916. MediumField:
  917. text: app.Obs['StrikeFreq'][2] + app.Obs['StrikeFreq'][3]
  918. pos_hint: {'x': 143/262, 'y': 7/202}
  919. size_hint_x: (114/262)
  920.  
  921. ## Lightning bolt icon
  922. Image:
  923. canvas.before:
  924. PushMatrix
  925. Translate:
  926. x: root.lightningBoltPosX
  927. canvas.after:
  928. PopMatrix
  929. source: 'atlas://atlas/lightningBolt/' + root.lightningBoltIcon + app.scaleSuffix
  930. keep_ratio: 0
  931. allow_stretch: 1
  932. pos_hint: {'x': 9/262, 'y': 120/202}
  933. size_hint: (65/262, 68/202)
  934.  
  935. <LightningButton>:
  936. PanelButton:
  937. id: 'LightningButton'
  938. text: 'Lightning'
  939. on_release: app.CurrentConditions.SwitchPanel(self)
  940.  
  941. ## =============================================================================
  942. ## BAROMETER WIDGET
  943. ## =============================================================================
  944. <BarometerPanel>:
  945.  
  946. ## Panel background, title, and Barometer dial
  947. PanelBackground:
  948. _panelTitle: 'Barometer'
  949. barometerDial:
  950.  
  951. ## Current pressure arrow
  952. Image:
  953. source: 'icons/barometer/icons' + app.scaleSuffix + '/' + root.barometerArrow + '.png'
  954. keep_ratio: 0
  955. allow_stretch: 1
  956. pos_hint: {'x': 46/262, 'y': 18/202}
  957. size_hint: (168/262, 84/202)
  958.  
  959. ## Maximum and minimum pressure on pressure dial
  960. TitleField:
  961. text: app.BarometerMin
  962. pos_hint: {'x': 29/262, 'y': 4/202}
  963. size_hint_x: (40/262)
  964. TitleField:
  965. text: app.BarometerMax
  966. pos_hint: {'x': 191/262, 'y': 4/202}
  967. size_hint_x: (40/262)
  968.  
  969. ## Current pressure
  970. TitleField:
  971. text: 'SLP'
  972. pos_hint: {'x': 80/262, 'y': 64/202}
  973. size_hint_x: (100/262)
  974. LargeField:
  975. text: app.Obs['Pres'][0]
  976. pos_hint: {'x': 80/262, 'y': 35/202}
  977. size_hint_x: (100/262)
  978. SmallField:
  979. text: app.Obs['Pres'][1]
  980. pos_hint: {'x': 80/262, 'y': 17/202}
  981. size_hint_x: (100/262)
  982.  
  983. ## Current pressure trend
  984. TitleField:
  985. text: 'Trend'
  986. pos_hint: {'x': 155/262, 'y': 166/202}
  987. size_hint_x: (96/262)
  988. SmallField:
  989. text: app.Obs['PresTrend'][2]
  990. pos_hint: {'x': 155/262, 'y': 148/202}
  991. size_hint_x: (96/262)
  992. SmallField:
  993. text: app.Obs['PresTrend'][0] + app.Obs['PresTrend'][1]
  994. pos_hint: {'x': 155/262, 'y': 129/202}
  995. size_hint_x: (96/262)
  996.  
  997. ## 24 Hour pressure low
  998. TitleField:
  999. text: 'Low'
  1000. pos_hint: {'x': 3/262, 'y': 166/202}
  1001. size_hint_x: (70/262)
  1002. SmallField:
  1003. text: '[color=00a4b4ff]' + app.Obs['MinPres'][0] + '[/color]'
  1004. pos_hint: {'x': 3/262, 'y': 148/202}
  1005. size_hint_x: (70/262)
  1006. SmallField:
  1007. text: app.Obs['MinPres'][2]
  1008. pos_hint: {'x': 3/262, 'y': 129/202}
  1009. size_hint_x: (70/262)
  1010.  
  1011. ## 24 Hour pressure high
  1012. TitleField:
  1013. text: 'High'
  1014. pos_hint: {'x': 73/262, 'y': 166/202}
  1015. size_hint_x: (70/262)
  1016. SmallField:
  1017. text: '[color=f05e40ff]' + app.Obs['MaxPres'][0] + '[/color]'
  1018. pos_hint: {'x': 73/262, 'y': 148/202}
  1019. size_hint_x: (70/262)
  1020. SmallField:
  1021. text: app.Obs['MaxPres'][2]
  1022. pos_hint: {'x': 73/262, 'y': 129/202}
  1023. size_hint_x: (70/262)
  1024.  
  1025. ## Weather tendency
  1026. SmallField:
  1027. text: app.Obs['PresTrend'][3]
  1028. pos_hint: {'x': 21/262, 'y': 109/202}
  1029. size_hint_x: (220/262)
  1030. <BarometerButton>:
  1031. PanelButton:
  1032. id: 'BarometerButton'
  1033. text: 'Pressure'
  1034. on_release: app.CurrentConditions.SwitchPanel(self)
  1035.  
  1036. ## =============================================================================
  1037. ## UPDATE NOTIFICATION
  1038. ## =============================================================================
  1039. <updateNotif>:
  1040. size_hint: (None, 76/480)
  1041. pos_hint: {'x': app.CurrentConditions.ids.PanelSix.x/app.window.width, 'y': 2/app.window.height}
  1042. width: app.CurrentConditions.ids.PanelSix.size[0]
  1043. UpdateNotifBackground:
  1044. _panelTitle: 'Update Available'
  1045. RelativeLayout:
  1046. BoldField:
  1047. text: 'Current version:'
  1048. pos_hint: {'x': 0.03, 'y': 0.5}
  1049. size_hint: (0.47, 14/76)
  1050. text_size: self.size
  1051. halign: 'left'
  1052. BoldField:
  1053. text: app.config['System']['Version']
  1054. pos_hint: {'x': 0.5, 'y': 0.52}
  1055. size_hint: (0.3, 14/76)
  1056. BoldField:
  1057. text: 'Latest version:'
  1058. pos_hint: {'x': 0.03, 'y': 0.28}
  1059. size_hint: (0.47, 14/76)
  1060. text_size: self.size
  1061. halign: 'left'
  1062. BoldField:
  1063. text: '[color=9aba2fff]' + app.Version['Latest'] + '[/color]'
  1064. pos_hint: {'x': 0.5, 'y': 0.30}
  1065. size_hint: (0.3, 14/76)
  1066. BoldField:
  1067. text: 'Run: [font=fonts/Inter-BoldItalic.ttf][color=00a4b4ff]wfpiconsole update[/color][/font]'
  1068. pos_hint: {'x': 0, 'y': 0.08}
  1069. size_hint: (1, 14/76)
  1070.  
  1071. ## =============================================================================
  1072. ## MAIN MENU
  1073. ## =============================================================================
  1074. <mainMenu>:
  1075. statusPanel: statusPanel.__self__
  1076. size_hint: (.8, .8)
  1077. canvas:
  1078. Color:
  1079. rgba: 0,0,0,1
  1080. Rectangle:
  1081. pos: self.pos
  1082. size: self.size
  1083. GridLayout:
  1084. padding: '5dp'
  1085. cols: 1
  1086. size_hint: None, None
  1087. pos: root.pos
  1088. size: root.size
  1089. TabbedPanel:
  1090. background_color: (0, 0, 0,0)
  1091. do_default_tab: False
  1092. tab_pos: 'bottom_left'
  1093. tab_height: 40*app.scaleFactor
  1094. tab_width: 100*app.scaleFactor
  1095. MainMenuTab:
  1096. text: 'System'
  1097. MainMenuLayout:
  1098. id: statusPanel
  1099. BoxLayout:
  1100. orientation: 'vertical'
  1101. padding: [dp(0),dp(0),dp(0),dp(10)]
  1102. size_hint: (1,.22)
  1103. MenuField:
  1104. font_size: dp(16*app.scaleFactor)
  1105. text: app.config['Station']['Name']
  1106. MenuField:
  1107. text: 'Station status: ' + app.Station.Device['stationStatus']
  1108. GridLayout:
  1109. cols: 2
  1110. orientation: 'vertical'
  1111. padding: [dp(0),dp(0),dp(0),dp(10)]
  1112. size_hint: (1,.28)
  1113. MenuField:
  1114. text: 'Station time: ' + app.System['Time']
  1115. MenuField:
  1116. text: 'Station elevation: ' + '{:.2f}'.format(float(app.config['Station']['Elevation'])) + ' m'
  1117. MenuField:
  1118. text: 'Station latitude: ' + '{:.4f}'.format(float(app.config['Station']['Latitude'])) + '\u00B0'
  1119. MenuField:
  1120. text: 'Station Longitude: ' + '{:.4f}'.format(float(app.config['Station']['Longitude'])) + '\u00B0'
  1121. MenuField:
  1122. text: 'Hub firmware: ' + app.Station.Device['hubFirmware']
  1123. MenuField:
  1124. text: 'Console version: [color=00a4b4ff]' + app.config['System']['Version'] + '[/color]'
  1125. BoxLayout:
  1126. orientation: 'horizontal'
  1127. size_hint: (1,.1)
  1128. StatusColumn:
  1129. size_hint: (.15,1)
  1130. text: 'Device'
  1131. StatusColumn:
  1132. size_hint: (.12,1)
  1133. text: 'Status'
  1134. StatusColumn:
  1135. size_hint: (.22,1)
  1136. text: 'Last Observation'
  1137. StatusColumn:
  1138. size_hint: (.20,1)
  1139. text: 'Battery Voltage'
  1140. StatusColumn:
  1141. size_hint: (.31,1)
  1142. text: '24 hr Observation Count'
  1143. MainMenuTab:
  1144. text: 'Credits'
  1145. MainMenuLayout:
  1146. CreditsMultilineField:
  1147. text:
  1148. 'Python Console for Weather Flow Tempest and Smart Home Weather Station \n' \
  1149. 'Code written by Peter Davis. Copyright 2018-2020 \n' \
  1150. 'GNU General Public License v3.0 \n\n' \
  1151. 'Graphics inspired by Weather34 Home Weather Station \n' \
  1152. 'Copyright 2015-2020 Brian Underdown \n' \
  1153. 'CC Attribution-NonCommercial-NoDerivatives 4.0 International License \n' \
  1154. 'www.weather34.com/homeweatherstation/ \n\n' \
  1155. 'Weather forecast data provided by \n' \
  1156. 'WeatherFlow BetterForecast API \n'
Advertisement
Add Comment
Please, Sign In to add comment