Skip to content

Commit c9049b0

Browse files
authored
Feedback on SD Card Logging (#305)
1 parent 6e80462 commit c9049b0

File tree

14 files changed

+467
-121
lines changed

14 files changed

+467
-121
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ repos:
2222
language: pygrep
2323
types: [python]
2424
files: ^pysquared/
25-
exclude: ^pysquared/beacon\.py|^pysquared/logger\.py|^pysquared/rtc/manager/microcontroller\.py
25+
exclude: ^pysquared/beacon\.py|^pysquared/logger\.py|^pysquared/rtc/manager/microcontroller\.py|pysquared/hardware/sd_card/manager/sd_card\.py
2626

2727
- repo: local
2828
hooks:

mocks/circuitpython/storage.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Mock for the CircuitPython storage module.
2+
3+
This module provides a mock implementation of the CircuitPython storage module
4+
for testing purposes. It allows for simulating the behavior of the storage
5+
module without the need for actual hardware.
6+
"""
7+
8+
9+
def disable_usb_drive() -> None:
10+
"""A mock function to disable the USB drive."""
11+
pass
12+
13+
14+
def enable_usb_drive() -> None:
15+
"""A mock function to enable the USB drive."""
16+
pass
17+
18+
19+
def remount(path: str, readonly: bool) -> None:
20+
"""A mock function to remount the filesystem.
21+
22+
Args:
23+
path: The path to remount.
24+
readonly: Whether to mount as read-only.
25+
"""
26+
pass

pyproject.toml

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,29 @@ docs = [
4545
[tool.setuptools]
4646
packages = [
4747
"pysquared",
48+
"pysquared.boot",
4849
"pysquared.config",
49-
"pysquared.hardware",
50-
"pysquared.hardware.imu",
50+
"pysquared.hardware.burnwire.manager",
51+
"pysquared.hardware.burnwire",
5152
"pysquared.hardware.imu.manager",
52-
"pysquared.hardware.magnetometer",
53+
"pysquared.hardware.imu",
54+
"pysquared.hardware.light_sensor.manager",
5355
"pysquared.hardware.magnetometer.manager",
54-
"pysquared.hardware.radio",
56+
"pysquared.hardware.magnetometer",
57+
"pysquared.hardware.power_monitor.manager",
58+
"pysquared.hardware.power_monitor",
5559
"pysquared.hardware.radio.manager",
5660
"pysquared.hardware.radio.packetizer",
57-
"pysquared.hardware.power_monitor",
58-
"pysquared.hardware.power_monitor.manager",
59-
"pysquared.hardware.temperature_sensor",
61+
"pysquared.hardware.radio",
62+
"pysquared.hardware.sd_card.manager",
63+
"pysquared.hardware.sd_card",
6064
"pysquared.hardware.temperature_sensor.manager",
61-
"pysquared.hardware.burnwire",
62-
"pysquared.hardware.burnwire.manager",
63-
"pysquared.hardware.light_sensor.manager",
65+
"pysquared.hardware.temperature_sensor",
66+
"pysquared.hardware",
6467
"pysquared.nvm",
6568
"pysquared.protos",
66-
"pysquared.rtc",
6769
"pysquared.rtc.manager",
70+
"pysquared.rtc",
6871
"pysquared.sensor_reading"
6972
]
7073

pysquared/beacon.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
from collections import OrderedDict
2020

2121
try:
22-
from mocks.circuitpython.microcontroller import Processor
23-
except ImportError:
2422
from microcontroller import Processor
23+
except ImportError:
24+
from mocks.circuitpython.microcontroller import Processor
2525

2626
from .hardware.radio.packetizer.packet_manager import PacketManager
2727
from .logger import Logger

pysquared/boot/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
This module provides utilities that can run during the boot process by adding them to boot.py.
3+
"""

pysquared/boot/filesystem.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""File includes utilities for managing the filesystem during the boot process."""
2+
3+
import os
4+
import time
5+
6+
try:
7+
import storage
8+
except ImportError:
9+
import mocks.circuitpython.storage as storage
10+
11+
12+
def mkdir(
13+
path: str,
14+
storage_action_delay: float = 0.02,
15+
) -> None:
16+
"""
17+
Create directories on internal storage during boot.
18+
19+
In CircuitPython the internal storage is not writable by default. In order to mount
20+
any external storage (such as an SD Card) the drive must be remounted in read/write mode.
21+
This function handles the necessary steps to safely create a directory on the internal
22+
storage during boot.
23+
24+
Args:
25+
mount_point: Path to mount point
26+
storage_action_delay: Delay after storage actions to ensure stability
27+
28+
Usage:
29+
```python
30+
from pysquared.boot.filesystem import mkdir
31+
mkdir("/sd")
32+
```
33+
"""
34+
try:
35+
storage.disable_usb_drive()
36+
time.sleep(storage_action_delay)
37+
print("Disabled USB drive")
38+
39+
storage.remount("/", False)
40+
time.sleep(storage_action_delay)
41+
print("Remounted root filesystem")
42+
43+
try:
44+
os.mkdir(path)
45+
print(f"Mount point {path} created.")
46+
except OSError:
47+
print(f"Mount point {path} already exists.")
48+
49+
finally:
50+
storage.enable_usb_drive()
51+
time.sleep(storage_action_delay)
52+
print("Enabled USB drive")

pysquared/boot_functions.py

Lines changed: 0 additions & 56 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
This module provides an interface for controlling SD cards.
3+
"""
Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
"""This module provides a SD Card class to manipulate the sd card filesystem"""
22

3-
# import os
4-
53
import sdcardio
64
import storage
5+
from busio import SPI
6+
from microcontroller import Pin
77

8-
from .hardware.exception import HardwareInitializationError
9-
10-
try:
11-
from busio import SPI
12-
from microcontroller import Pin
13-
except ImportError:
14-
pass
8+
from ...exception import HardwareInitializationError
159

1610

1711
class SDCardManager:
@@ -26,13 +20,9 @@ def __init__(
2620
baudrate: int = 400000,
2721
mount_path: str = "/sd",
2822
) -> None:
29-
self.mounted = False
30-
self.mount_path = mount_path
31-
3223
try:
3324
sd = sdcardio.SDCard(spi_bus, chip_select, baudrate)
34-
vfs = storage.VfsFat(sd)
35-
storage.mount(vfs, self.mount_path)
36-
self.mounted = True
37-
except (OSError, ValueError) as e:
25+
vfs = storage.VfsFat(sd) # type: ignore # Issue: https://github.com/adafruit/Adafruit_CircuitPython_Typing/issues/51
26+
storage.mount(vfs, mount_path)
27+
except Exception as e:
3828
raise HardwareInitializationError("Failed to initialize SD Card") from e

pysquared/logger.py

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import traceback
1919
from collections import OrderedDict
2020

21-
import adafruit_pathlib
22-
2321
from .nvm.counter import Counter
2422

2523

@@ -79,31 +77,25 @@ class LogLevel:
7977
class Logger:
8078
"""Handles logging messages with different severity levels."""
8179

80+
_log_dir: str | None = None
81+
8282
def __init__(
8383
self,
8484
error_counter: Counter,
85-
sd_path: adafruit_pathlib.Path = None,
86-
# sd_card: SDCardManager = None,
8785
log_level: int = LogLevel.NOTSET,
8886
colorized: bool = False,
8987
) -> None:
9088
"""
9189
Initializes the Logger instance.
9290
9391
Args:
94-
error_counter (Counter): Counter for error occurrences.
95-
log_level (int): Initial log level.
96-
colorized (bool): Whether to colorize output.
92+
error_counter: Counter for error occurrences.
93+
log_level: Initial log level.
94+
colorized: Whether to colorize output.
9795
"""
9896
self._error_counter: Counter = error_counter
99-
self.sd_path: adafruit_pathlib.Path = sd_path
10097
self._log_level: int = log_level
101-
self.colorized: bool = colorized
102-
103-
try:
104-
self.sd_path = self.sd_path / "logs"
105-
except TypeError as e:
106-
print(f"path not set: {e}")
98+
self._colorized: bool = colorized
10799

108100
def _can_print_this_level(self, level_value: int) -> bool:
109101
"""
@@ -162,30 +154,15 @@ def _log(self, level: str, level_value: int, message: str, **kwargs) -> None:
162154

163155
json_order.update(kwargs)
164156

165-
try:
166-
json_output = json.dumps(json_order)
167-
except TypeError as e:
168-
json_output = json.dumps(
169-
OrderedDict(
170-
[
171-
("time", asctime),
172-
("level", "ERROR"),
173-
("msg", f"Failed to serialize log message: {e}"),
174-
]
175-
),
176-
)
157+
json_output = json.dumps(json_order)
177158

178159
if self._can_print_this_level(level_value):
179-
# Write to sd card if mounted
180-
if self.path:
181-
if "logs" not in self.sd_path.iterdir():
182-
print("/sd/logs does not exist, creating...")
183-
os.mkdir("/sd/logs")
184-
185-
with open("/sd/logs/activity.log", "a") as f:
160+
if self._log_dir is not None:
161+
file = self._log_dir + os.sep + "activity.log"
162+
with open(file, "a") as f:
186163
f.write(json_output + "\n")
187164

188-
if self.colorized:
165+
if self._colorized:
189166
json_output = json_output.replace(
190167
f'"level": "{level}"', f'"level": "{LogColors[level]}"'
191168
)
@@ -256,3 +233,26 @@ def get_error_count(self) -> int:
256233
int: The number of errors logged.
257234
"""
258235
return self._error_counter.get()
236+
237+
def set_log_dir(self, log_dir: str) -> None:
238+
"""
239+
Sets the log directory for file logging.
240+
241+
Args:
242+
log_dir (str): Directory to save log files.
243+
244+
Raises:
245+
ValueError: If the provided path is not a valid directory.
246+
"""
247+
try:
248+
# Octal number 0o040000 is the stat mode indicating the file being stat'd is a directory
249+
directory_mode: int = 0o040000
250+
st_mode = os.stat(log_dir)[0]
251+
if st_mode != directory_mode:
252+
raise ValueError(
253+
f"Logging path must be a directory, received {st_mode}."
254+
)
255+
except OSError as e:
256+
raise ValueError("Invalid logging path.") from e
257+
258+
self._log_dir = log_dir

0 commit comments

Comments
 (0)