aalh

mem_aloc_fails_in_tick

Nov 8th, 2025
266
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.04 KB | Source Code | 0 0
  1. # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
  2. #
  3. # SPDX-License-Identifier: MIT
  4.  
  5. """
  6. PyPortal based alarm clock.
  7.  
  8. Adafruit invests time and resources providing this open source code.
  9. Please support Adafruit and open source hardware by purchasing
  10. products from Adafruit!
  11.  
  12. Written by Dave Astels for Adafruit Industries
  13. Copyright (c) 2019 Adafruit Industries
  14. Licensed under the MIT license.
  15.  
  16. All text above must be included in any redistribution.
  17. """
  18.  
  19. # pylint:disable=redefined-outer-name,no-member,global-statement, line-too-long, invalid-name
  20. # pylint:disable=no-self-use,too-many-branches,too-many-statements, import-error, global-variable-not-assigned
  21. # pylint:disable=useless-super-delegation, too-many-locals, no-name-in-module, C0200
  22.  
  23. import math
  24. import time
  25. import board
  26. from adafruit_bitmap_font import bitmap_font # type: ignore
  27. from adafruit_display_text.label import Label # pyright: ignore[reportMissingImports]
  28. import adafruit_sgp40 # pyright: ignore[reportMissingImports]
  29. import analogio
  30. # import displayio
  31. import adafruit_logging as logging # pyright: ignore[reportMissingImports]
  32. import bitbangio
  33. import rtc
  34. import adafruit_gps # pyright: ignore[reportMissingImports]
  35. import neopixel # pyright: ignore[reportMissingImports]
  36. from microcontroller import nvm
  37. from adafruit_bme280 import basic as bmE280 # pyright: ignore[reportMissingImports]
  38. import adafruit_sht31d # type: ignore
  39. import adafruit_lps2x # type: ignore
  40. from adafruit_pyportal import PyPortal # type: ignore
  41.  
  42. # read time zone
  43. if nvm[0] > 23 or nvm[0] < 0: # pyright: ignore[reportOptionalSubscript]
  44.     TZ_CUR = 4
  45.     nvm[0] = 4 # pyright: ignore[reportOptionalSubscript]
  46. else:
  47.     TZ_CUR = nvm[0]  # type: ignore
  48. ####################
  49. # setup hardware
  50. # i2c = board.I2C()
  51. # i2c = busio.I2C(board.SCL, board.SDA, frequency=100_000)
  52.  
  53. # bitbangio used due to its implementation working better (less i2c errors)
  54. i2c = bitbangio.I2C(board.SCL, board.SDA, frequency=100_000)  # type: ignore
  55. print("i2c")
  56. gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False)  # type: ignore # Use I2C interface
  57. print("gps")
  58. gps.send_command(b"PMTK314,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
  59. gps.send_command(b"PMTK220,1000")
  60.  
  61. pixels = neopixel.NeoPixel(board.NEOPIXEL, 1)  # type: ignore
  62. bmE280.sea_level_pressure = 1013.25  # type: ignore
  63. outside = adafruit_sht31d.SHT31D(i2c)
  64. print("sht31d")
  65. inside = bmE280.Adafruit_BME280_I2C(i2c)  # type: ignore
  66. print("bme280")
  67. lps = adafruit_lps2x.LPS22(i2c)
  68. print("lps")
  69. sgp = adafruit_sgp40.SGP40(i2c)
  70. print("sgp40")
  71. gps.update()
  72. while gps.timestamp_utc is None:
  73.     print("No time data from GPS yet")
  74.     pixels[0] = (0, 0, 0)
  75.     time.sleep(.7)
  76.     pixels[0] = (128, 0, 0)
  77.     time.sleep(.7)
  78.     gps.update()
  79.     print(gps.readline())
  80. pixels[0] = (0, 0, 0)
  81. the_rtc = rtc.RTC()
  82. the_rtc.datetime = time.struct_time(gps.timestamp_utc)
  83.  
  84.  
  85. pyportal = PyPortal()
  86.  
  87. light = analogio.AnalogIn(board.LIGHT)  # type: ignore
  88. nobacklight = False
  89. # init logfile
  90. # t_stamp = "{0:x}".format(time.time())
  91. DELIMETER = "^"
  92. LOG_FILE = f"/sd/{time.time()}gps.txt"
  93. LOG_MODE = "a"
  94. outfile = open(LOG_FILE, LOG_MODE, encoding="utf-8")  # type: ignore
  95. print("UTC",DELIMETER,"lat",DELIMETER,"long",DELIMETER,"alt",DELIMETER,"Speed",DELIMETER,"intemp",DELIMETER,"intemp2",DELIMETER,"inh",DELIMETER,"otemp",DELIMETER,"outh",DELIMETER,"barr1",DELIMETER,"barr2",DELIMETER,"airQ", file=outfile)
  96.  
  97. print("envlogfile created")
  98. ####################
  99. # variables
  100. Speed = 0
  101.  
  102.  
  103.  
  104. # weather support
  105.  
  106. icon_file = None
  107. icon_sprite = None
  108. celcius = None
  109.  
  110.  
  111. # display/data refresh timers
  112.  
  113. refresh_time = None
  114. update_time = None
  115. weather_refresh = None
  116.  
  117. # The most recently fetched time
  118. current_time = None
  119.  
  120. # track whether we're in low light mode
  121.  
  122. low_light = False
  123.  
  124. oldbacklight = 1
  125. ####################
  126. # Load the fonts
  127. print("loading fonts")
  128. time_font = bitmap_font.load_font('/fonts/Asimov-104.pcf')
  129. time_font.load_glyphs(b'0123456789:%')  # pre-load glyphs for fast printing  # type: ignore
  130.  
  131. alarm_font = bitmap_font.load_font('/fonts/AsimovNar-45.pcf')
  132. alarm_font.load_glyphs(b'0123456789mph.:%')  # type: ignore
  133.  
  134. title_font = bitmap_font.load_font('/fonts/Asimov-25.pcf')
  135. title_font.load_glyphs(b'INSIDEOUT')  # type: ignore
  136.  
  137. temperature_font = bitmap_font.load_font('/fonts/AsimovNar-40.pcf')
  138. temperature_font.load_glyphs(b'0123456789INSIDEOUTFft.%')  # type: ignore
  139.  
  140. print("fonts loaded")
  141. ####################
  142. # Set up logging
  143.  
  144. logger = logging.getLogger('alarm_clock')
  145. logger.addHandler(logging.FileHandler("/sd/syslogz.txt", "a"))
  146. logger.setLevel(logging.INFO)            # change as desired  # type: ignore
  147. print("logger loaded")
  148. ####################
  149. # Functions
  150. # def debug(*args, **kwargs):
  151.     # lets both print and write to the file
  152.     # print(*args, **kwargs)
  153.     # print(*args, **kwargs, file=outfile)  # type: ignore
  154.  
  155. def create_text_areas(configs):
  156.     """Given a list of area specifications, create and return text areas."""
  157.     print("creating text areas")
  158.     text_areas = []
  159.     for cfg in configs:
  160.         textarea = Label(cfg['font'], text=' '*cfg['size'])
  161.         textarea.x = cfg['x']
  162.         textarea.y = cfg['y']
  163.         textarea.color = cfg['color']
  164.         text_areas.append(textarea)
  165.     return text_areas
  166.  
  167. def calc_dewpoint(humidity, temp_c):
  168.     """Calculate dew point given relative humidity and temperature in Celsius."""
  169.     a = 17.625
  170.     b = 243.04
  171.     alpha = math.log(humidity/100.0) + ((a * temp_c) / (b + temp_c))
  172.     return (b * alpha) / (a - alpha)
  173.  
  174. def clear_splash():
  175.     """clear the screen"""
  176.     print("clear_splash")
  177.     for _ in range(len(pyportal.root_group) - 1):
  178.         pyportal.root_group.pop()
  179.  
  180.  
  181. def touch_in_button(t, b):
  182.     """touch"""
  183.     print(f"touch_in_button: t={t}, b={b}")
  184.     in_horizontal = b['left'] <= t[0] <= b['right']
  185.     in_vertical = b['top'] <= t[1] <= b['bottom']
  186.     return in_horizontal and in_vertical
  187.  
  188.  
  189. touched = False
  190.  
  191. ####################
  192. # states
  193.  
  194. class State(object):
  195.     """State abstract base class"""
  196.  
  197.     def __init__(self):
  198.         pass
  199.  
  200.     @property
  201.     def name(self):
  202.         """Return the name of teh state"""
  203.         return ''
  204.  
  205.     def tick(self, now):
  206.         """Handle a tick: one pass through the main loop"""
  207.  
  208.     # pylint:disable=unused-argument
  209.     def touch(self, t, touched):
  210.  
  211.         """Handle a touch event.
  212.        :param (x, y, z) - t: the touch location/strength"""
  213.         return bool(t)
  214.  
  215.     def enter(self):
  216.         """Just after the state is entered."""
  217.         print(State)
  218.  
  219.     def exit(self):
  220.         """Just before the state exits."""
  221.         clear_splash()
  222.  
  223. class Time_State(State):
  224.     """This state manages the primary time display screen/mode"""
  225.  
  226.     def __init__(self):
  227.         super().__init__()
  228.         self.background_day = None
  229.         self.background_night = None
  230.         self.refresh_time = None
  231.         self.update_time = None
  232.         self.weather_refresh = None
  233.         text_area_configs = [dict(x=50, y=235, size=5, color=0xFFFFFF, font=time_font),  # TIME
  234.                              dict(x=325, y=100, size=5, color=0xE09941, font=alarm_font),  # SPEED
  235.                              dict(x=120, y=10, size=16, color=0xCFFC03, font=title_font),  # screen heading
  236.                              dict(x=125, y=48, size=6, color=0xFFFFFF, font=temperature_font),  # INSIDE temp
  237.                              dict(x=125, y=93, size=6, color=0x4F0066, font=temperature_font),  # INSIDE humid
  238.                              dict(x=215, y=48, size=6, color=0xFFFFFF, font=temperature_font),  # OUTSIDE temp
  239.                              dict(x=215, y=93, size=6, color=0x4F0066, font=temperature_font),  # OUTSIDE humid
  240.                              dict(x=145, y=170, size=12, color=0x32A89E, font=temperature_font),  # Altitude
  241.                              dict(x=310, y=180, size=5, color=0xCFFC03, font=title_font)]  # Air Quality
  242.         self.text_areas = create_text_areas(text_area_configs)
  243.         # self.weather_icon = displayio.Group()
  244.         # self.weather_icon.x = 125
  245.         # self.weather_icon.y = 25
  246.         # self.icon_file = None
  247.         print("creating buttons")
  248.  
  249.         # each button has it's edges as well as the state to transition to when touched
  250.         self.buttons = [dict(left=380, top=0, right=479, bottom=158, next_state='backlight'),
  251.                         dict(left=0, top=0, right=118, bottom=158, next_state='settings')]
  252.                        #  dict(left=0, top=208, right=118, bottom=294, next_state='pressurel')
  253.  
  254.     @property
  255.     def name(self):
  256.         return 'time'
  257.  
  258.     def adjust_backlight_based_on_light(self, force=False):
  259.         """Check light level. Adjust the backlight and background image if it's dark."""
  260.         global low_light, nobacklight
  261.         if light.value <= 800 and (force or not low_light) and nobacklight is False:
  262.             pyportal.set_backlight(0.01)
  263.             pyportal.set_background(self.background_night)
  264.             low_light = True
  265.         elif (force or (light.value >= 2000 and low_light)) and nobacklight is False:
  266.             pyportal.set_backlight(1.00)
  267.             pyportal.set_background(self.background_day)
  268.             low_light = False
  269.  
  270.     def tick(self, now):
  271.         global update_time, outfile  # type: ignore
  272.  
  273.         # check light level and adjust background & backlight
  274.         self.adjust_backlight_based_on_light()
  275.  
  276.         # only query the online time once per hour (and on first run)
  277.         if (not self.refresh_time) or ((now - self.refresh_time) > 300):
  278.             logger.debug('Fetching time')
  279.             try:
  280.                 gps.update()
  281.                 the_rtc.datetime = time.struct_time(gps.timestamp_utc)  # type: ignore
  282.                 self.refresh_time = now
  283.             except RuntimeError as e:
  284.                 self.refresh_time = now - 3000   # delay 10 minutes before retrying
  285.                 logger.error('Some error occured, retrying! - %s', str(e))
  286.  
  287.         # only query the weather every 10 minutes (and on first run)
  288.         # if (not self.weather_refresh) or (now - self.weather_refresh) > 1:
  289.         print("reading sensors")
  290.         logger.debug('Fetching weather')
  291.         try:
  292.             gps.update()
  293.             if gps.speed_knots is not None:
  294.                 Speed = round(gps.speed_knots * 1.151,0)  # type: ignore
  295.                 speed_str = f"{Speed:5} mph"
  296.             else:
  297.                 Speed = 0
  298.                 speed_str = f"{Speed:5d} mph"
  299.             self.text_areas[1].text = speed_str
  300.             i1 = (inside.temperature * 9 / 5) + 32
  301.             i2 = (lps.temperature * 9 / 5) + 32
  302.             intemp = f"{round(i1)} F"
  303.             intemp2 = f"{round(i2):3d} F"
  304.             it_text = f"{round((i1 + i2) / 2):3d} F"
  305.             self.text_areas[2].text = "INSIDE  OUTSIDE"
  306.             self.text_areas[3].text = it_text
  307.             inrh = inside.relative_humidity
  308.             humi_text = f"{round((calc_dewpoint(inside.relative_humidity, inside.temperature)* 9 / 5) + 32 ):2d}"
  309.             self.text_areas[4].text = humi_text
  310.             outtemp = outside.temperature
  311.             ot_text = f"{round(((outtemp * 9 / 5) + 32)):3d} F"  # type: ignore
  312.             self.text_areas[5].text = ot_text
  313.             humo_text = f"{round((calc_dewpoint(outside.relative_humidity, outside.temperature)* 9 / 5) + 32 ):2d}"           # type: ignore
  314.             # print("humo_text:", (calc_dewpoint(outside.relative_humidity, outside.temperature)* 9 / 5) + 32)
  315.             self.text_areas[6].text = humo_text
  316.             VOCIdx = sgp.measure_index(temperature = inside.temperature, relative_humidity = inrh)
  317.             self.text_areas[8].text = f"{round(VOCIdx):3d} voc"
  318.             if gps.altitude_m is not None:
  319.                 altftxt = f"{round(gps.altitude_m * 3.28084, 0):6} ft"
  320.             else:
  321.                 altftxt = "0"
  322.             self.text_areas[7].text = altftxt
  323.             self.weather_refresh = now
  324.             t_stamp = time.time()
  325.             print(t_stamp, DELIMETER,
  326.                 gps.latitude, DELIMETER,
  327.                 gps.longitude, DELIMETER,
  328.                 altftxt, DELIMETER,
  329.                 Speed, DELIMETER,
  330.                 intemp, DELIMETER,
  331.                 intemp2, DELIMETER,
  332.                 humi_text, DELIMETER,
  333.                 ot_text, DELIMETER,
  334.                 humo_text, DELIMETER,
  335.                 inside.pressure, DELIMETER,
  336.                 lps.pressure, DELIMETER,
  337.                 VOCIdx,
  338.                 file=outfile)
  339.             try:
  340.                 board.DISPLAY.refresh(target_frames_per_second=60)  # type: ignore
  341.             except AttributeError:
  342.                 board.DISPLAY.refresh_soon()  # type: ignore
  343.                 board.DISPLAY.wait_for_frame()  # type: ignore
  344.         except RuntimeError as e:
  345.             self.weather_refresh = now - 540   # delay a minute before retrying
  346.             logger.error("Some error occured, retrying! - %s", str(e))
  347.  
  348.         if (not update_time) or ((now - update_time) > 30):
  349.             # Update the time
  350.             update_time = now
  351.             current_time = time.localtime()
  352.             time_string = f"{((current_time.tm_hour-TZ_CUR) % 24):02d}:{current_time.tm_min:02d}"
  353.             self.text_areas[0].text = time_string
  354.  
  355.             try:
  356.                 board.DISPLAY.refresh(target_frames_per_second=60)  # type: ignore
  357.             except AttributeError:
  358.                 board.DISPLAY.refresh_soon()  # type: ignore
  359.                 board.DISPLAY.wait_for_frame()  # type: ignore
  360.  
  361.     def touch(self, t, touched):
  362.         if t and not touched:             # only process the initial touch
  363.             for button_index in range(len(self.buttons)):
  364.                 b = self.buttons[button_index]
  365.                 if touch_in_button(t, b):
  366.                     change_to_state(b['next_state'])  # type: ignore
  367.                     break
  368.         return bool(t)
  369.  
  370.     def enter(self):
  371.         self.adjust_backlight_based_on_light(force=True)
  372.         for ta in self.text_areas:
  373.             pyportal.root_group.append(ta)
  374.         # pyportal.root_group.append(self.weather_icon)
  375.         try:
  376.             board.DISPLAY.refresh(target_frames_per_second=60)  # type: ignore
  377.         except AttributeError:
  378.             board.DISPLAY.refresh_soon()  # type: ignore
  379.             board.DISPLAY.wait_for_frame()  # type: ignore
  380.  
  381.     def exit(self):
  382.         super().exit()
  383.  
  384. """
  385. class Pressure_State(Time_State):
  386.  
  387.    def __init__(self):
  388.        super().__init__()
  389.        text_area_configs = [dict(x=160, y=220, size=3, color=0xFFAAFF, font=time_font)]
  390.  
  391.        self.text_areas = create_text_areas(text_area_configs)
  392.  
  393.    @property
  394.    def name(self):
  395.        return 'pressurel'
  396.  
  397.    def tick(self, now):
  398.        # Once the job is done, go back to the main screen
  399.        change_to_state('time')  # type: ignore
  400.  
  401.    def enter(self):
  402.        global low_light
  403.        for ta in self.text_areas:
  404.            pyportal.root_group.append(ta)
  405.  
  406.        low_light = False
  407.        plog_path = "/sd/"
  408.        # pyportal.set_backlight(1.00)
  409.        # pyportal.set_background(mugsy_background)
  410.        lps.data_rate = adafruit_lps2x.Rate.LPS22_RATE_75_HZ  # type: ignore
  411.        gps.update()
  412.        t_stamp = time.time()
  413.        PLOG_FILE = f"{t_stamp}pressure.txt"
  414.        start = time.monotonic_ns() / 1e9
  415.        with open((plog_path+PLOG_FILE), LOG_MODE, encoding="utf-8") as pressfile:
  416.            print("timestamp, latitude, longitude, altitude_m, \n", file=pressfile)
  417.            print(time.time(), gps.latitude, gps.longitude, gps.altitude_m, file=pressfile)
  418.            curr_time = 0
  419.            while curr_time < 60:
  420.                curr_time = (time.monotonic_ns() / 1e9) - start
  421.                print(curr_time, lps.pressure, file=pressfile)
  422.                cntdwn = f"{round((60 - curr_time)):2d}"
  423.                self.text_areas[0].text = cntdwn
  424.                try:
  425.                    board.DISPLAY.refresh(target_frames_per_second=60)  # type: ignore
  426.                except AttributeError:
  427.                    board.DISPLAY.refresh_soon()  # type: ignore
  428.                    board.DISPLAY.wait_for_frame()  # type: ignore
  429.  
  430.        lps.data_rate = adafruit_lps2x.Rate.LPS22_RATE_1_HZ  # type: ignore
  431. """
  432.  
  433. class Setting_State(State):
  434.     """This state lets the user adjust UTC offset."""
  435.     def __init__(self):
  436.         super().__init__()
  437.         self.previous_touch = None
  438.         self.background = 'settings_backgroundl.bmp'
  439.         text_area_configs = [dict(x=160, y=220, size=5, color=0xFFFFFF, font=time_font)]
  440.  
  441.         self.text_areas = create_text_areas(text_area_configs)
  442.         self.buttons = [dict(left=0, top=0, right=200, bottom=123),    # increase
  443.                         dict(left=0, top=130, right=200, bottom=180),   # return
  444.                         dict(left=0, top=190, right=200, bottom=320)]  # decrease
  445.                         # dict(left=150, top=0, right=300, bottom=320),  # hours
  446.                         # dict(left=310, top=0, right=480, bottom=320)]   # minutes
  447.         time_string = f"{TZ_CUR:2d}"
  448.         self.text_areas[0].text = time_string
  449.  
  450.     @property
  451.     def name(self):
  452.         return 'settings'
  453.  
  454.     def touch(self, t, touched):
  455.         global TZ_CUR
  456.         if t:
  457.             if touch_in_button(t, self.buttons[0]):   # increase offset
  458.                 logger.debug('increase offset')
  459.                 TZ_CUR += 1
  460.                 time_string = f"{TZ_CUR:2d}"
  461.                 self.text_areas[0].text = time_string
  462.             elif touch_in_button(t, self.buttons[1]):   # return
  463.                 logger.debug('RETURN touched')
  464.                 nvm[0] = TZ_CUR  # type: ignore
  465.                 change_to_state('time')
  466.             elif touch_in_button(t, self.buttons[2]):  # decrease offset
  467.                 logger.debug('decrease offset')
  468.                 TZ_CUR -= 1
  469.                 time_string = f"{TZ_CUR:2d}"
  470.                 self.text_areas[0].text = time_string
  471.             try:
  472.                 board.DISPLAY.refresh(target_frames_per_second=60)  # type: ignore
  473.             except AttributeError:
  474.                 board.DISPLAY.refresh_soon()  # type: ignore
  475.                 board.DISPLAY.wait_for_frame()  # type: ignore
  476.  
  477.         else:
  478.             self.previous_touch = None
  479.         return bool(t)
  480.  
  481.     def enter(self):
  482.  
  483.         pyportal.set_background(self.background)
  484.         for ta in self.text_areas:
  485.             pyportal.root_group.append(ta)
  486.         # if alarm_enabled:
  487.             # self.text_areas[0].text = '%02d:%02d' % (alarm_hour, alarm_minute)  # set time textarea
  488.         # else:
  489.             # self.text_areas[0].text = '     '
  490.  
  491. class Backlight_Power(Time_State):
  492.     """This state toggles backlight """
  493.  
  494.     def __init__(self):
  495.         super().__init__()
  496.  
  497.     @property
  498.     def name(self):
  499.         return 'backlight'
  500.  
  501.     def tick(self, now):
  502.         # Once the job is done, go back to the main screen
  503.         change_to_state('time')  # type: ignore
  504.  
  505.     def enter(self):
  506.         global nobacklight, oldbacklight
  507.         if board.DISPLAY.brightness < 0:
  508.             oldbacklight = board.DISPLAY.brightness
  509.             board.DISPLAY.brightness = 0
  510.             nobacklight = True
  511.             return()
  512.         board.DISPLAY.brightness =oldbacklight
  513.         nobacklight = False
  514.         # pyportal.set_background(mugsy_background)
  515.  
  516. ####################
  517. # State management
  518.  
  519. states = {'time': Time_State(),
  520.           # 'pressurel': Pressure_State(),
  521.           'settings': Setting_State(),
  522.           'backlight': Backlight_Power()}
  523.  
  524. current_state = None
  525.  
  526.  
  527. def change_to_state(state_name):
  528.     """."""
  529.     global current_state
  530.     if current_state:
  531.         logger.debug('Exiting %s', current_state.name)
  532.         current_state.exit()
  533.     current_state = states[state_name]
  534.     logger.debug('Entering %s', current_state.name)
  535.     current_state.enter()
  536.  
  537. ####################
  538. # And... go
  539.  
  540. clear_splash()
  541. change_to_state("time")
  542.  
  543. try:
  544.     while True:
  545.         touched = current_state.touch(pyportal.touchscreen.touch_point, touched)  # type: ignore
  546.         current_state.tick(time.monotonic())  # type: ignore
  547. finally:
  548.     outfile.close()
  549.  
Advertisement
Add Comment
Please, Sign In to add comment