demure_clock.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. #!/usr/bin/env python3
  2. ## demure's fancy led matrix clock -- python sample
  3. ## This is a fancy led matrix clock written for a pi with an led matrix and
  4. ## and ada fruit matrix hat.
  5. ## NOTE: This python version is mostly a test, and the C version is the primary code.
  6. ## This is due to the python being pretty slow, especially on a pi zero.
  7. ## TODO:
  8. ## * Wrap weather request in `try` and supply fail case json sample
  9. ## * Get RGBMatrixOptions working
  10. ## * Remove object oriented junk?
  11. ## * Rename default instance
  12. ## * Ensure weather icons match nightmode
  13. ##
  14. ## * add wakeup mode? like not super bright before 0800 weekday, 1000 weekend
  15. ## * make config.json and move openweather api_key/city_id. maybe have nightmode time conf
  16. from samplebase import SampleBase
  17. from rgbmatrix import graphics
  18. from rgbmatrix import RGBMatrix, RGBMatrixOptions
  19. import time
  20. # from datetime import date
  21. from datetime import datetime
  22. import requests, json
  23. from pathlib import Path
  24. from PIL import Image
  25. from PIL import ImageDraw
  26. ## Configuration for the matrix
  27. options = RGBMatrixOptions()
  28. options.rows = 64
  29. options.cols = 64
  30. options.chain_length = 1
  31. options.parallel = 1
  32. options.hardware_mapping = 'adafruit-hat'
  33. options.gpio_slowdown = 4
  34. # matrix = RGBMatrix(options=options)
  35. ## Prep Dirs
  36. base_dir = Path(__file__).parent
  37. font_dir = str(base_dir) + '/spleen/'
  38. icon_dir = str(base_dir) + '/weathericons/'
  39. ## Load config.json
  40. with open(base_dir / "config.json") as f:
  41. try:
  42. conf = json.load(f)
  43. except:
  44. print("Error: invalid json in config.json")
  45. quit()
  46. base_url = conf["base_url"]
  47. api_key = conf["api_key"]
  48. city_id = conf["city_id"]
  49. global complete_url
  50. complete_url = base_url + "appid=" + api_key + "&id=" + city_id
  51. # x = json.loads('{"weather": [{"main": "NULL", "description": "NULL", "icon": "NULL"}], "main": {"temp": 0, "feels_like": 0, "temp_min": 0, "temp_max": 0, "pressure": 0, "humidity": 0}, "clouds": {"all": 0}, "dt": 0, "sys": {"sunrise": 0, "sunset": 0}, "cod": 0}')
  52. ### Night Mode Check Function ### {{{
  53. def night_mode_check(now, c_up, c_sunrise, c_sunset):
  54. now_s = int(now.strftime("%s"))
  55. hour = int(now.strftime("%H"))
  56. ## Set night mode if after sunset/before sunrise,
  57. ## as long as last weather pull is less than 6 hours old.
  58. ## Fallback of after 2200 and before 0700.
  59. if (c_sunset != 0 and (now_s - c_up) < 21600):
  60. if (now_s > c_sunset or now_s < c_sunrise):
  61. night_mode = True
  62. else:
  63. night_mode = False
  64. else:
  65. if (hour >= 22 or hour < 7):
  66. night_mode = True
  67. else:
  68. night_mode = False
  69. return night_mode
  70. ### End Night Mode Check Function ### }}}
  71. ### Wakeup Mode Check Function ### {{{
  72. def wakeup_mode_check(now, c_up):
  73. now_s = int(now.strftime("%s"))
  74. hour = int(now.strftime("%H"))
  75. time = int(now.strftime("%H%M"))
  76. day = int(now.strftime("%w")) ## return 0 thru 6; 0 = sun, 6 = sat
  77. ## Weekday wakeup ends at 0800
  78. ## Weekend wakeup ends at 1000
  79. if (day == 0 or day == 6):
  80. ## four hours (6 to 10) = 14400 sec
  81. wake_s = int(datetime.fromisoformat(now.strftime('%Y-%m-%d 10:00:00')).strftime('%s'))
  82. # if (hour < 10):
  83. if (time < 1300):
  84. wake_math = (1 - ((wake_s - now_s) / 14400))
  85. wake_mode = round(wake_math, 2)
  86. else:
  87. wake_mode = 1
  88. else:
  89. ## two hours (6 to 8) = 7200 sec
  90. wake_s = int(datetime.fromisoformat(now.strftime('%Y-%m-%d 08:00:00')).strftime('%s'))
  91. if (hour < 22):
  92. wake_math = (1 - ((wake_s - now_s) / 7200))
  93. wake_mode = round(wake_math, 2)
  94. else:
  95. wake_mode = 1
  96. ## Sanitize in case we get a negative value
  97. if (wake_mode < 0):
  98. wake_mode = 1
  99. return wake_mode
  100. ### End Wakeup Mode Check Function ### }}}
  101. ### Wakeup Mode Color Function ### {{{
  102. def wakeup_mode_color(wake_mode):
  103. temp_red = int((125 * wake_mode) + 25)
  104. temp_green = int(75 * wake_mode)
  105. tc_wake = graphics.Color(temp_red, temp_green, 0)
  106. return tc_wake
  107. ### End Wakeup Mode Color Function ### }}}
  108. def drawimage(canvas, path, x, y):
  109. image = Image.open(path).convert('RGB')
  110. image.load()
  111. # matrix.SetImage(image, x, y)
  112. canvas.SetImage(image, x, y)
  113. class RunText(SampleBase):
  114. def __init__(self, *args, **kwargs):
  115. super(RunText, self).__init__(*args, **kwargs)
  116. # self.parser.add_argument("-t", "--text", help="The text to scroll on the RGB LED panel", default="Hello world!")
  117. def run(self):
  118. canvas = self.matrix.CreateFrameCanvas()
  119. ### Set Fonts ### {{{
  120. ft_sp_12x24 = graphics.Font()
  121. ft_sp_12x24.LoadFont(font_dir + 'spleen-12x24.bdf')
  122. ft_sp_5x8 = graphics.Font()
  123. ft_sp_5x8.LoadFont(font_dir + 'spleen-5x8.bdf')
  124. ft_sp_16x32 = graphics.Font()
  125. ft_sp_16x32.LoadFont(font_dir + 'spleen-16x32.bdf')
  126. ft_sp_8x16 = graphics.Font()
  127. ft_sp_8x16.LoadFont(font_dir + 'spleen-8x16.bdf')
  128. ### End Set Fonts ### }}}
  129. ## Initialize before first weather fetch
  130. x = json.loads('{"cod": 0}')
  131. c_up = int(datetime.now().strftime("%s")) - 840 ## Off set to reduce initial weather load delay.
  132. # c_up = int(datetime.now().strftime("%s")) - 890 ## DEBUG
  133. c_sunrise = 0
  134. c_sunset = 0
  135. while True:
  136. canvas.Clear()
  137. ## Initialize Time Vars
  138. now = datetime.now()
  139. now_s = int(now.strftime("%s"))
  140. utcnow = datetime.utcnow()
  141. hour = int(now.strftime("%H"))
  142. ### Pull Weather Info ### {{{
  143. # if (int(now.strftime("%s")) - int(c_up)) > 10: ## for DEBUG
  144. if (now_s - c_up) > 900: ## Refresh every 15min
  145. graphics.DrawText(canvas, ft_sp_5x8, 0, 12, graphics.Color(0,35,0), "u") ## Print update indicator
  146. ## Request Weather
  147. try:
  148. response = requests.get(complete_url)
  149. # response = requests.get("http://test.lan") ##DEBUG
  150. x = response.json()
  151. except:
  152. print("weather fail") ##DEBUG
  153. x = json.loads('{"weather": [{"main": "NULL", "description": "NULL", "icon": "NULL"}], "main": {"temp": 0, "feels_like": 0, "temp_min": 0, "temp_max": 0, "pressure": 0, "humidity": 0}, "clouds": {"all": 0}, "dt": 0, "sys": {"sunrise": 0, "sunset": 0}, "cod": 0}')
  154. # x["dt"] = now_s ##DEBUG
  155. if x["cod"] != 0 and x["cod"] != 404:
  156. c_temp = x["main"]["temp"]
  157. c_temp_f = round((c_temp - 273.15) * 9/5 + 32)
  158. c_humid = x["main"]["humidity"]
  159. c_icon = x["weather"][0]["icon"]
  160. c_cond = x["weather"][0]["id"]
  161. # c_desc = x["weather"][0]["description"]
  162. c_sunrise = int(x["sys"]["sunrise"])
  163. c_sunset = int(x["sys"]["sunset"])
  164. c_up = int(x["dt"])
  165. else:
  166. ## Stop weather pulls until normal delay has occured.
  167. c_up = now_s
  168. ### End Pull Weather Info ### }}}
  169. ## Test for night mode
  170. night_mode = night_mode_check(now, c_up, c_sunrise, c_sunset)
  171. ## Test for wakeup mode
  172. if(night_mode == False):
  173. wake_mode = wakeup_mode_check(now, c_up)
  174. else:
  175. wake_mode = 1
  176. ### Text Colors ### {{{
  177. ## Set Day/Night Colors
  178. if (night_mode == True):
  179. tc_night = graphics.Color(25, 0, 0)
  180. tc_time = tc_night
  181. tc_week = tc_night
  182. tc_date = tc_night
  183. tc_utc = tc_night
  184. tc_epoch = tc_night
  185. tc_temp = tc_night
  186. tc_humid = tc_night
  187. tc_sun = tc_night
  188. elif (wake_mode < 1):
  189. tc_wake = wakeup_mode_color(wake_mode)
  190. tc_time = tc_wake
  191. tc_week = tc_wake
  192. tc_date = tc_wake
  193. tc_utc = tc_wake
  194. tc_epoch = tc_wake
  195. tc_temp = tc_wake
  196. tc_humid = tc_wake
  197. tc_sun = tc_wake
  198. else:
  199. tc_time = graphics.Color(0, 0, 200)
  200. tc_week = graphics.Color(0, 125, 125)
  201. tc_date = graphics.Color(125, 0, 125)
  202. tc_utc = graphics.Color(150, 0, 0)
  203. tc_epoch = graphics.Color(0, 150, 0)
  204. tc_temp = graphics.Color(125, 125, 125)
  205. tc_humid = graphics.Color(125, 125, 0)
  206. tc_sun = graphics.Color(150, 75, 0)
  207. ### Text Colors ### }}}
  208. ### Draw Day/Night/Late-Night Modes ### {{{
  209. ## Late-Night Output
  210. if (hour <= 6):
  211. # if True: ## DEBUG
  212. graphics.DrawText(canvas, ft_sp_16x32, 0, 20, tc_time, now.strftime("%H%M"))
  213. graphics.DrawText(canvas, ft_sp_16x32, 0, 42, tc_week, now.strftime("%a"))
  214. ## Day Display Weather Info
  215. if x["cod"] != 0 and x["cod"] != 404:
  216. graphics.DrawText(canvas, ft_sp_16x32, 0, 64, tc_temp, str(c_temp_f) + "F")
  217. ## Day/Night Output
  218. else:
  219. ## where, font, Y, X, color, string
  220. graphics.DrawText(canvas, ft_sp_12x24, 2, 15, tc_time, now.strftime("%R"))
  221. graphics.DrawText(canvas, ft_sp_5x8, 0, 43, tc_week, now.strftime("%a, %b"))
  222. graphics.DrawText(canvas, ft_sp_5x8, 0, 50, tc_date, now.strftime("%F"))
  223. graphics.DrawText(canvas, ft_sp_5x8, 0, 57, tc_utc, utcnow.strftime("%R UTC"))
  224. graphics.DrawText(canvas, ft_sp_5x8, 0, 64, tc_epoch, now.strftime("%s"))
  225. ## Day Display Weather Info
  226. if x["cod"] != 0 and x["cod"] != 404:
  227. graphics.DrawText(canvas, ft_sp_8x16, 0, 27, tc_temp, str(c_temp_f) + "F")
  228. graphics.DrawText(canvas, ft_sp_5x8, 0, 35, tc_humid, str(c_humid) + "%H")
  229. ## Either sunrise or sunset time
  230. if (now_s < c_sunrise):
  231. graphics.DrawText(canvas, ft_sp_5x8, 25, 35, tc_sun, str(datetime.fromtimestamp(c_sunrise).strftime("%H%M")) + "*")
  232. elif (now_s < c_sunset):
  233. graphics.DrawText(canvas, ft_sp_5x8, 25, 35, tc_sun, str(datetime.fromtimestamp(c_sunset).strftime("%H%M")) + "*")
  234. else:
  235. pass
  236. ##TODO: handle error by displaying something?
  237. # graphics.DrawText(canvas, ft_sp_5x8, 0, 36, tc_temp, "fail")
  238. ### End Draw Day/Night/Late-Night Modes ### }}}
  239. ## Display weather icon if not late night mode
  240. if x["cod"] != 0 and x["cod"] != 404:
  241. # if night_mode == False:
  242. if not (hour <= 6) or not (wake_mode == 1):
  243. if c_cond == 900:
  244. drawimage(canvas, icon_dir + 'tornado' + '.png', 49, 17)
  245. elif c_cond == 901 or c_cond == 902:
  246. drawimage(canvas, icon_dir + 'hurricane' + '.png', 49, 17)
  247. elif c_cond == 906 or c_cond == 611 or c_cond == 612:
  248. drawimage(canvas, icon_dir + 'hail' + '.png', 49, 17)
  249. elif c_cond == 600 or c_cond == 601 or c_cond == 602:
  250. drawimage(canvas, icon_dir + 'snow' + '.png', 49, 17)
  251. else:
  252. drawimage(canvas, icon_dir + c_icon + '.png', 49, 17)
  253. # time.sleep(0.05)
  254. time.sleep(0.1)
  255. # time.sleep(0.5)
  256. canvas = self.matrix.SwapOnVSync(canvas)
  257. # Main function
  258. if __name__ == "__main__":
  259. run_text = RunText()
  260. if (not run_text.process()):
  261. run_text.print_help()