rpi-app-framework 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 John Singer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: rpi-app-framework
3
+ Version: 0.1.2
4
+ Summary: A Python framework for building applications on all Raspberry Pi versions, including Pico.
5
+ Home-page: https://github.com/yourusername/rpi-app-framework
6
+ Author: Your Name
7
+ Author-email: John Singer <john@singerlinks.com>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/jsinger0420/rpi-app-framework
10
+ Project-URL: Repository, https://github.com/jsinger0420/rpi-app-framework.git
11
+ Project-URL: Documentation, https://github.com/jsinger0420/rpi-app-framework/wiki
12
+ Project-URL: Bug Tracker, https://github.com/jsinger0420/rpi-app-framework/issues
13
+ Keywords: raspberry-pi,pico,framework,app-development,iot
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
24
+ Classifier: Topic :: System :: Hardware
25
+ Requires-Python: >=3.6
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Provides-Extra: pi
29
+ Requires-Dist: RPi.GPIO>=0.7.0; extra == "pi"
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=7.0; extra == "test"
32
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
33
+ Provides-Extra: dev
34
+ Requires-Dist: black>=23.0; extra == "dev"
35
+ Requires-Dist: isort>=5.0; extra == "dev"
36
+ Requires-Dist: flake8>=6.0; extra == "dev"
37
+ Dynamic: author
38
+ Dynamic: home-page
39
+ Dynamic: license-file
40
+ Dynamic: requires-python
41
+
42
+ RPi App Framework
43
+ Modular framework for all Raspberry Pi models (Pico 2 W with MicroPython/Thonny, full RPi 1-5 with Python).
44
+ Installation
45
+ For MicroPython/Pico (Thonny):
46
+
47
+ Copy the rpi_app_framework folder to /lib on your Pico.
48
+
49
+ For full Python/RPi:
50
+
51
+ pip install rpi-app-framework
52
+
53
+ Usage
54
+ Create a main.py to start your app:
55
+ # examples/simple_demo.py
56
+ from rpi_app_framework import RPIApp, PiHardwareAdapter
57
+ import time
58
+
59
+ class SimpleDemo(RPIApp):
60
+ """
61
+ Minimal demo that shows:
62
+ • Board model detection
63
+ • CPU temperature monitoring
64
+ • On-board LED blinking
65
+ Works on Pico 2 W and all full-size Raspberry Pi models.
66
+ """
67
+
68
+ def setup(self):
69
+ # Initialise the hardware adapter
70
+ self.hw = PiHardwareAdapter(log_func=self.log)
71
+
72
+ # Use the onboard LED (works on Pico and full RPi)
73
+ self.status_led = self.hw.led("LED" if "Pico" in self.hw.model else 13)
74
+
75
+ # Log startup info
76
+ self.log(f"Running on: {self.hw.model}")
77
+ self.log(f"Initial CPU temperature: {self.hw.cpu_temperature}°C")
78
+
79
+ def run(self):
80
+ while self.running:
81
+ # Blink the LED
82
+ self.status_led.value(1)
83
+ time.sleep(0.5)
84
+ self.status_led.value(0)
85
+
86
+ # Log temperature every 5 seconds
87
+ self.log(f"CPU temperature: {self.hw.cpu_temp}°C")
88
+ time.sleep(4.5)
89
+
90
+ if __name__ == "__main__":
91
+ SimpleDemo(max_log_files=10).start()
92
+
93
+ Compatibility
94
+
95
+ Pico 2 W (MicroPython): Uses machine for pins/PWM.
96
+ Full RPi (Python): Uses RPi.GPIO and gpiozero for pins/PWM.
97
+
98
+ Features
99
+
100
+ RPIApp: Base class for app lifecycle and logging.
101
+ DeviceManager: Base for hardware managers with logging.
102
+ LEDSimple: Controls LEDs (on/off/blink).
103
+ WiFiManager: Manages WiFi connections (Pico only).
104
+ MotorDriverTB6612FNG: Controls dual DC motors.
105
+ MicrodotManager: Runs a lightweight web server.
106
+
107
+ For example applications see projects at www.singerlinks.com
@@ -0,0 +1,66 @@
1
+ RPi App Framework
2
+ Modular framework for all Raspberry Pi models (Pico 2 W with MicroPython/Thonny, full RPi 1-5 with Python).
3
+ Installation
4
+ For MicroPython/Pico (Thonny):
5
+
6
+ Copy the rpi_app_framework folder to /lib on your Pico.
7
+
8
+ For full Python/RPi:
9
+
10
+ pip install rpi-app-framework
11
+
12
+ Usage
13
+ Create a main.py to start your app:
14
+ # examples/simple_demo.py
15
+ from rpi_app_framework import RPIApp, PiHardwareAdapter
16
+ import time
17
+
18
+ class SimpleDemo(RPIApp):
19
+ """
20
+ Minimal demo that shows:
21
+ • Board model detection
22
+ • CPU temperature monitoring
23
+ • On-board LED blinking
24
+ Works on Pico 2 W and all full-size Raspberry Pi models.
25
+ """
26
+
27
+ def setup(self):
28
+ # Initialise the hardware adapter
29
+ self.hw = PiHardwareAdapter(log_func=self.log)
30
+
31
+ # Use the onboard LED (works on Pico and full RPi)
32
+ self.status_led = self.hw.led("LED" if "Pico" in self.hw.model else 13)
33
+
34
+ # Log startup info
35
+ self.log(f"Running on: {self.hw.model}")
36
+ self.log(f"Initial CPU temperature: {self.hw.cpu_temperature}°C")
37
+
38
+ def run(self):
39
+ while self.running:
40
+ # Blink the LED
41
+ self.status_led.value(1)
42
+ time.sleep(0.5)
43
+ self.status_led.value(0)
44
+
45
+ # Log temperature every 5 seconds
46
+ self.log(f"CPU temperature: {self.hw.cpu_temp}°C")
47
+ time.sleep(4.5)
48
+
49
+ if __name__ == "__main__":
50
+ SimpleDemo(max_log_files=10).start()
51
+
52
+ Compatibility
53
+
54
+ Pico 2 W (MicroPython): Uses machine for pins/PWM.
55
+ Full RPi (Python): Uses RPi.GPIO and gpiozero for pins/PWM.
56
+
57
+ Features
58
+
59
+ RPIApp: Base class for app lifecycle and logging.
60
+ DeviceManager: Base for hardware managers with logging.
61
+ LEDSimple: Controls LEDs (on/off/blink).
62
+ WiFiManager: Manages WiFi connections (Pico only).
63
+ MotorDriverTB6612FNG: Controls dual DC motors.
64
+ MicrodotManager: Runs a lightweight web server.
65
+
66
+ For example applications see projects at www.singerlinks.com
@@ -0,0 +1,56 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "rpi-app-framework"
7
+ version = "0.1.2"
8
+ description = "A Python framework for building applications on all Raspberry Pi versions, including Pico."
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = "MIT"
12
+ authors = [
13
+ {name = "John Singer", email = "john@singerlinks.com"}
14
+ ]
15
+ keywords = ["raspberry-pi", "pico", "framework", "app-development", "iot"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.7",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
27
+ "Topic :: System :: Hardware"
28
+ ]
29
+ dependencies = []
30
+
31
+ [project.optional-dependencies]
32
+ pi = [
33
+ "RPi.GPIO>=0.7.0"
34
+ ]
35
+ test = [
36
+ "pytest>=7.0",
37
+ "pytest-cov>=4.0"
38
+ ]
39
+ dev = [
40
+ "black>=23.0",
41
+ "isort>=5.0",
42
+ "flake8>=6.0"
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/jsinger0420/rpi-app-framework"
47
+ Repository = "https://github.com/jsinger0420/rpi-app-framework.git"
48
+ Documentation = "https://github.com/jsinger0420/rpi-app-framework/wiki"
49
+ "Bug Tracker" = "https://github.com/jsinger0420/rpi-app-framework/issues"
50
+
51
+ [tool.setuptools]
52
+ package-dir = {"" = "src"}
53
+ packages = {find = {where = ["src"]}}
54
+
55
+ [tool.setuptools.package-data]
56
+ "rpi_app_framework" = ["*.txt", "*.md"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,29 @@
1
+ from setuptools import setup, find_packages
2
+ import os
3
+
4
+ setup(
5
+ name="rpi-app-framework",
6
+ version="0.1.0",
7
+ author="Your Name",
8
+ author_email="your.email@example.com",
9
+ description="Modular framework for all Raspberry Pi models with hardware managers (LED, WiFi, Motor, Web)",
10
+ long_description=open("README.md").read() if os.path.exists("README.md") else "A cross-compatible framework for RPi Pico and full RPi apps.",
11
+ long_description_content_type="text/markdown",
12
+ url="https://github.com/yourusername/rpi-app-framework",
13
+ packages=find_packages(), # Auto-discovers rpi_app_framework/
14
+ classifiers=[
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Development Status :: 4 - Beta",
19
+ "Topic :: System :: Hardware",
20
+ ],
21
+ python_requires=">=3.6",
22
+ install_requires=[
23
+ "RPi.GPIO; sys_platform == 'linux'", # For full RPi GPIO
24
+ "gpiozero; sys_platform == 'linux'", # For PWM/LED on full RPi
25
+ ],
26
+ extras_require={
27
+ "web": ["microdot"], # For MicrodotManager
28
+ },
29
+ )
@@ -0,0 +1,10 @@
1
+ from .rpi_app import RPIApp
2
+ from .device_manager import DeviceManager
3
+ from .led_simple import LEDSimple
4
+ from .wifi_manager import WiFiManager
5
+ from .motor_driver_tb6612 import MotorDriverTB6612FNG
6
+ from .microdot_manager import MicrodotManager
7
+ from .hardware import PiHardwareAdapter
8
+
9
+ __version__ = "0.1.1"
10
+ __all__ = ['RPIApp', 'DeviceManager', 'LEDSimple', 'WiFiManager', 'MotorDriverTB6612FNG', 'MicrodotManager','PiHardwareAdapter']
@@ -0,0 +1,84 @@
1
+ # Conditional imports for cross-platform compatibility
2
+ try:
3
+ from machine import Pin, PWM
4
+ MICROPYTHON = True
5
+ except ImportError:
6
+ import RPi.GPIO as GPIO
7
+ from gpiozero import LED, PWMOutputDevice as PWMLED
8
+ MICROPYTHON = False
9
+
10
+ class DeviceManager:
11
+ """
12
+ Abstract base class for device managers.
13
+ Provides common logging functionality and device naming with validation.
14
+ Supports Pico (MicroPython) and full RPi (Python) via conditional imports.
15
+ """
16
+
17
+ def __init__(self, name=None, log_func=None):
18
+ """
19
+ Initialize the DeviceManager instance.
20
+
21
+ :param name: Optional custom name for the device instance (defaults to class name).
22
+ Must be a non-empty string, max 50 characters, alphanumeric with spaces/underscores/dashes.
23
+ :param log_func: Optional function to log messages (e.g., app's log method).
24
+ :raises ValueError: If name is invalid.
25
+ """
26
+ self.logging_enabled = True # Default to logging enabled
27
+ self.log_func = log_func
28
+ self._validate_name(name)
29
+ self._name = name or self.__class__.__name__ # Use provided name or class name
30
+
31
+ def _validate_name(self, name):
32
+ """
33
+ Validate the device name.
34
+
35
+ :param name: The name to validate.
36
+ :raises ValueError: If name is invalid (not a string, empty, too long, or contains invalid characters).
37
+ """
38
+ if name is None:
39
+ return # Will default to class name, which is valid
40
+ if not isinstance(name, str):
41
+ raise ValueError("Device name must be a string")
42
+ if len(name.strip()) == 0:
43
+ raise ValueError("Device name cannot be empty")
44
+ if len(name) > 50:
45
+ raise ValueError("Device name must be 50 characters or fewer")
46
+ # Allow alphanumeric, spaces, underscores, dashes; no other special chars for log/file safety
47
+ import re
48
+ if not re.match(r'^[a-zA-Z0-9 _\-]+$', name):
49
+ raise ValueError("Device name can only contain alphanumeric characters, spaces, underscores, and dashes")
50
+
51
+ @property
52
+ def name(self):
53
+ """
54
+ Get the device name.
55
+
56
+ :return: The name of the device.
57
+ """
58
+ return self._name
59
+
60
+ def enable_logging(self):
61
+ """
62
+ Enable logging for the device.
63
+ """
64
+ self.logging_enabled = True
65
+
66
+ def disable_logging(self):
67
+ """
68
+ Disable logging for the device.
69
+ """
70
+ self.logging_enabled = False
71
+
72
+ def _log(self, message):
73
+ """
74
+ Internal method to log a message if logging is enabled.
75
+ Prefixes the message with the device name.
76
+
77
+ :param message: The message to log.
78
+ """
79
+ if self.logging_enabled:
80
+ prefixed_message = f"[{self.name}] {message}"
81
+ if self.log_func:
82
+ self.log_func(prefixed_message)
83
+ else:
84
+ print(prefixed_message)
@@ -0,0 +1,132 @@
1
+ # rpi_app_framework/pi_hardware_adapter.py
2
+ from device_manager import DeviceManager
3
+
4
+ try:
5
+ from machine import Pin, PWM
6
+ MICROPYTHON = True
7
+ except ImportError:
8
+ import RPi.GPIO as GPIO
9
+ from gpiozero import LED, PWMOutputDevice
10
+ GPIO.setmode(GPIO.BCM)
11
+ MICROPYTHON = False
12
+
13
+ import subprocess
14
+ import re
15
+
16
+
17
+ class PiHardwareAdapter(DeviceManager):
18
+ """
19
+ Unified hardware adapter for all Raspberry Pi models.
20
+ Provides:
21
+ • Pin factory (digital out/in, PWM)
22
+ • Board model detection
23
+ • CPU temperature
24
+ Works on Pico 2 W (MicroPython) and full-size Raspberry Pi (Python).
25
+ """
26
+
27
+ def __init__(self, name="PiHardwareAdapter", log_func=None):
28
+ super().__init__(name=name, log_func=log_func)
29
+ self._model = self._detect_model()
30
+ self._log(f"Hardware: {self._model}")
31
+
32
+ def _detect_model(self) -> str:
33
+ if MICROPYTHON:
34
+ return "Raspberry Pi Pico 2 W"
35
+ try:
36
+ with open("/proc/device-tree/model", "r") as f:
37
+ model = f.read().strip("\x00")
38
+ return model or "Raspberry Pi (unknown)"
39
+ except Exception:
40
+ return "Raspberry Pi (detection failed)"
41
+
42
+ @property
43
+ def model(self) -> str:
44
+ """Read-only: detected board model."""
45
+ return self._model
46
+
47
+ @property
48
+ def cpu_temperature(self) -> float:
49
+ """Read-only: CPU/core temperature in °C."""
50
+ if MICROPYTHON:
51
+ # RP2040 built-in sensor
52
+ try:
53
+ sensor = machine.ADC(4)
54
+ reading = sensor.read_u16()
55
+ voltage = reading * 3.3 / 65535
56
+ temp = 27 - (voltage - 0.706) / 0.001721
57
+ return round(temp, 2)
58
+ except Exception as e:
59
+ raise RuntimeError(f"Pico temp sensor error: {e}")
60
+
61
+ # Full-size RPi
62
+ try:
63
+ out = subprocess.check_output(["vcgencmd", "measure_temp"], text=True)
64
+ m = re.search(r"temp=([\d.]+)", out)
65
+ if m:
66
+ return float(m.group(1))
67
+ except Exception:
68
+ pass
69
+ try:
70
+ with open("/sys/class/thermal/thermal_zone0/temp") as f:
71
+ return round(int(f.read().strip()) / 1000.0, 2)
72
+ except Exception:
73
+ raise RuntimeError("CPU temperature unavailable")
74
+
75
+ @property
76
+ def cpu_temp(self) -> float:
77
+ """Alias for cpu_temperature."""
78
+ return self.cpu_temperature
79
+
80
+ # ——— Pin Abstraction ———
81
+
82
+ def digital_out(self, pin, value=None):
83
+ """Return a digital output pin object (or set value directly)."""
84
+ if MICROPYTHON:
85
+ p = Pin(pin, Pin.OUT)
86
+ if value is not None:
87
+ p.value(value)
88
+ return p
89
+ else:
90
+ GPIO.setup(pin, GPIO.OUT)
91
+ if value is not None:
92
+ GPIO.output(pin, value)
93
+ return lambda v=None: GPIO.output(pin, v) if v is not None else None
94
+
95
+ def digital_in(self, pin, pull=None):
96
+ """Return a digital input pin (with optional pull-up/down)."""
97
+ if MICROPYTHON:
98
+ mode = Pin.IN
99
+ if pull == "up":
100
+ mode = Pin.IN | Pin.PULL_UP
101
+ elif pull == "down":
102
+ mode = Pin.IN | Pin.PULL_DOWN
103
+ return Pin(pin, mode)
104
+ else:
105
+ pull_mode = GPIO.PUD_UP if pull == "up" else GPIO.PUD_DOWN if pull == "down" else GPIO.PUD_OFF
106
+ GPIO.setup(pin, GPIO.IN, pull_up_down=pull_mode)
107
+ return lambda: GPIO.input(pin)
108
+
109
+ def pwm(self, pin, freq=1000, duty=0):
110
+ """Return a PWM output object."""
111
+ if MICROPYTHON:
112
+ p = PWM(Pin(pin))
113
+ p.freq(freq)
114
+ p.duty_u16(int(duty * 655.35)) # 0–100 → 0–65535
115
+ return p
116
+ else:
117
+ pwm_obj = PWMOutputDevice(pin, frequency=freq)
118
+ pwm_obj.value = duty / 100.0
119
+ return pwm_obj
120
+
121
+ def led(self, pin):
122
+ """Convenient factory for an LED (digital out)."""
123
+ if MICROPYTHON:
124
+ return self.digital_out(pin)
125
+ else:
126
+ return LED(pin)
127
+
128
+ def cleanup(self):
129
+ """Clean up GPIO resources (full RPi only)."""
130
+ if not MICROPYTHON:
131
+ GPIO.cleanup()
132
+ self._log("GPIO cleaned up")
@@ -0,0 +1,83 @@
1
+ # Conditional imports for cross-platform compatibility
2
+ try:
3
+ from machine import Pin
4
+ MICROPYTHON = True
5
+ except ImportError:
6
+ import RPi.GPIO as GPIO
7
+ from gpiozero import LED
8
+ MICROPYTHON = False
9
+
10
+ from .device_manager import DeviceManager
11
+ import time
12
+
13
+ class LEDSimple(DeviceManager):
14
+ """
15
+ Simple LED manager without PWM support.
16
+ Inherits from DeviceManager for logging and naming.
17
+ Provides basic on/off and blink functionality.
18
+ Works on Pico (MicroPython) or full RPi (Python).
19
+ """
20
+
21
+ def __init__(self, pin="LED", name=None, log_func=None):
22
+ """
23
+ Initialize the LEDSimple instance.
24
+
25
+ :param pin: Pin to use for the LED (default: "LED").
26
+ :param name: Optional custom name for this LED instance.
27
+ :param log_func: Optional function to log messages (e.g., app's log method).
28
+ """
29
+ super().__init__(name=name, log_func=log_func)
30
+ if MICROPYTHON:
31
+ self.led = Pin(pin, Pin.OUT)
32
+ else:
33
+ self.led_pin = int(pin)
34
+ GPIO.setmode(GPIO.BCM)
35
+ GPIO.setup(self.led_pin, GPIO.OUT)
36
+ self.led = LED(self.led_pin) # Use gpiozero for easy control
37
+ self.off() # Ensure LED is off initially
38
+
39
+ def on(self):
40
+ """
41
+ Turn the LED on.
42
+ """
43
+ if MICROPYTHON:
44
+ self.led.value(1)
45
+ else:
46
+ self.led.on()
47
+ self._log("LED turned on")
48
+
49
+ def off(self):
50
+ """
51
+ Turn the LED off.
52
+ """
53
+ if MICROPYTHON:
54
+ self.led.value(0)
55
+ else:
56
+ self.led.off()
57
+ self._log("LED turned off")
58
+
59
+ def toggle(self):
60
+ """
61
+ Toggle the LED state (on to off or off to on).
62
+ """
63
+ if MICROPYTHON:
64
+ self.led.toggle()
65
+ state = "on" if self.led.value() else "off"
66
+ else:
67
+ self.led.toggle()
68
+ state = "on" if self.led.is_active else "off"
69
+ self._log(f"LED toggled to {state}")
70
+
71
+ def blink(self, duration=0.5, count=1):
72
+ """
73
+ Blink the LED a specified number of times.
74
+
75
+ :param duration: Duration of each blink in seconds (default: 0.5).
76
+ :param count: Number of blinks (default: 1).
77
+ """
78
+ for _ in range(count):
79
+ self.on()
80
+ time.sleep(duration)
81
+ self.off()
82
+ time.sleep(duration)
83
+ self._log(f"LED blinked {count} times with duration {duration}s")
@@ -0,0 +1,81 @@
1
+ # Conditional imports for cross-platform compatibility
2
+ try:
3
+ from microdot_asyncio import Microdot
4
+ MICROPYTHON = True
5
+ except ImportError:
6
+ from microdot import Microdot # Full Python version
7
+ MICROPYTHON = False
8
+
9
+ from .device_manager import DeviceManager
10
+ import asyncio
11
+
12
+ class MicrodotManager(DeviceManager):
13
+ """
14
+ Manager class for running a Microdot web server on Raspberry Pi (Pico 2 W or full RPi).
15
+ Inherits from DeviceManager for logging and naming.
16
+ Assumes WiFi is already connected (e.g., via WiFiManager).
17
+ Supports adding routes and running the server asynchronously.
18
+ """
19
+
20
+ def __init__(self, name="Web Server", log_func=None, port=80):
21
+ """
22
+ Initialize the MicrodotManager instance.
23
+
24
+ :param name: Optional custom name for this manager instance (default: "Web Server").
25
+ :param log_func: Optional function to log messages (e.g., app's log method).
26
+ :param port: Port to run the web server on (default: 80).
27
+ """
28
+ super().__init__(name=name, log_func=log_func)
29
+ self.app = Microdot()
30
+ self.port = port
31
+ self._log(f"Microdot web server initialized on port {port}")
32
+
33
+ def setup(self):
34
+ """
35
+ Setup method for MicrodotManager.
36
+ Can be overridden to add routes or configure the server.
37
+ """
38
+ self._log("MicrodotManager setup complete")
39
+
40
+ def add_route(self, path, handler, methods=['GET']):
41
+ """
42
+ Add a route to the web server.
43
+
44
+ :param path: URL path for the route (e.g., '/status').
45
+ :param handler: Function to handle the route (receives request object).
46
+ :param methods: List of HTTP methods (e.g., ['GET', 'POST']).
47
+ """
48
+ try:
49
+ self.app.route(path, methods=methods)(handler)
50
+ self._log(f"Added route: {path} ({methods})")
51
+ except Exception as e:
52
+ self._log(f"Error adding route {path}: {e}")
53
+ raise
54
+
55
+ async def run_server_async(self):
56
+ """
57
+ Run the Microdot server asynchronously.
58
+ Suitable for MicroPython's asyncio or full Python.
59
+ """
60
+ try:
61
+ if MICROPYTHON:
62
+ await self.app.start_server(port=self.port)
63
+ else:
64
+ self.app.run(port=self.port) # Full Python runs synchronously
65
+ self._log(f"Web server running on port {self.port}")
66
+ except Exception as e:
67
+ self._log(f"Error running web server: {e}")
68
+ raise
69
+
70
+ def run(self):
71
+ """
72
+ Synchronous wrapper to run the server (for compatibility).
73
+ Starts the async server using asyncio.
74
+ """
75
+ try:
76
+ asyncio.run(self.run_server_async())
77
+ except KeyboardInterrupt:
78
+ self._log("Web server stopped by user")
79
+ except Exception as e:
80
+ self._log(f"Error in web server: {e}")
81
+ raise