Skip to content

Commit cf5bbfa

Browse files
Add README.md and draft_code
1 parent e4372a9 commit cf5bbfa

File tree

11 files changed

+1063
-538
lines changed

11 files changed

+1063
-538
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
PiggyOS
2+
=======
3+
4+
This is an operating system for microcontrollers like the ESP32.
5+
6+
It's written entirely in MicroPython, including device drivers, interrupt handlers, boot code, multitasking, display handling.
7+
8+
The architecure is inspired by the Android operating system for smartphones:
9+
- 'thin' operating system with facilities for apps
10+
- 'everything is an app' idea
11+
- making it as simple as possible for developers to build new apps
12+
13+
## Apps
14+
15+
The operating system comes with a few apps built-in that are necessary to bootstrap:
16+
- launcher: to be able to start apps
17+
- wificonf: to be able to connect to the wifi
18+
- appstore: to be able to download and install new apps
19+
20+
Furthermore, these apps are also built-in for convenience:
21+
- osupdate: to download and install operating system updates
22+
- camera: to take pictures and videos
23+
- imutest: to test the Inertial Measurement Unit (accelerometer)
24+
25+
## Supported hardware
26+
27+
- https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-2
28+
29+
## Architecture
30+
31+
- boot.py: initializes the hardware
32+
- main.py: initializes the User Interface, contains helper functions for apps, and starts the launcher app
33+
34+
## Filesystem layout:
35+
36+
- /apps: new apps are downloaded and installed here
37+
- /apps/com.example.app1: example app1 installation directory
38+
- /apps/com.example.app1/MANIFEST.MF: info about app1 such as Name, Start-Script
39+
- /apps/com.example.app1/mipmap-mdpi: medium dpi images for app1
40+
- /apps/com.example.app1/mipmap-mdpi/icon_64x64.bin: icon for app1 (64x64 pixels)
41+
- /builtin/: read-only filesystem that's compiled in and mounted at boot by main.py
42+
- /builtin/apps: apps that are builtin and necessary for minimal facilities (launcher, wificonf, appstore etc)
43+
- /builtin/res/mipmap-mdpi/default_icon_64x64.bin: default icon for apps that don't have one
44+

draft_code/include_in_build.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
print("This script will be included in the build.")
2+
print("You can then run it with: import include_in_build")

draft_code/microdot_webserver.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
2+
3+
4+
#import mip
5+
#mip.install('github:miguelgrinberg/microdot/src/microdot/microdot.py')
6+
# http://192.168.1.122/upload
7+
# http://192.168.1.122/files//
8+
9+
10+
from microdot import Microdot, Response
11+
import os
12+
13+
14+
# HTML template for the upload form
15+
UPLOAD_FORM = '''
16+
<!DOCTYPE html>
17+
<html>
18+
<head><title>File Manager</title></head>
19+
<body>
20+
<h1>File Manager</h1>
21+
<p><a href="/files">View Files</a></p>
22+
<h2>Upload File</h2>
23+
<form method="POST" action="/upload" enctype="multipart/form-data">
24+
<input type="file" name="file">
25+
<input type="submit" value="Upload">
26+
</form>
27+
</body>
28+
</html>
29+
'''
30+
31+
app = Microdot()
32+
33+
@app.route('/')
34+
async def index(request):
35+
return '<a href="/files//">Files</a>'
36+
37+
38+
@app.route('/files/<path:path>')
39+
async def list_files(req, path=''):
40+
try:
41+
# Sanitize path to prevent directory traversal
42+
path = path.strip('/')
43+
if '..' in path:
44+
return Response('Invalid path', status_code=400)
45+
# Get directory contents
46+
full_path = '/' + path
47+
files = os.listdir(full_path) if path else os.listdir('/')
48+
html = '<h1>File Manager</h1><ul>'
49+
for f in files:
50+
link_path = f'{path}/{f}' if path else f
51+
html += f'<li><a href="/files/{link_path}">{f}</a> | <a href="/download/{link_path}">Download</a></li>'
52+
html += '</ul>'
53+
return Response(html, headers={'Content-Type': 'text/html'})
54+
except OSError as e:
55+
return Response(f'Error: {e}', status_code=500)
56+
57+
58+
@app.route('/download/<path:path>')
59+
async def download_file(req, path):
60+
try:
61+
full_path = '/' + path.strip('/')
62+
with open(full_path, 'rb') as f:
63+
content = f.read() # Read in chunks for large files
64+
return Response(content, headers={
65+
'Content-Type': 'application/octet-stream',
66+
'Content-Disposition': f'attachment; filename="{path.split("/")[-1]}"'
67+
})
68+
except OSError as e:
69+
return Response(f'Error: {e}', status_code=404)
70+
71+
72+
73+
@app.route('/upload', methods=['GET'])
74+
async def upload_form(req):
75+
return Response(UPLOAD_FORM, headers={'Content-Type': 'text/html'})
76+
77+
78+
79+
@app.route('/upload', methods=['POST'])
80+
async def upload_file(req):
81+
try:
82+
# Check if form data contains a file
83+
if 'file' not in req.form or not req.files['file']['filename']:
84+
return Response('No file selected', status_code=400)
85+
# Get file details
86+
filename = req.files['file']['filename']
87+
file_content = req.files['file']['body']
88+
# Sanitize filename to prevent path traversal
89+
filename = filename.split('/')[-1].split('\\')[-1]
90+
if not filename:
91+
return Response('Invalid filename', status_code=400)
92+
# Save file to filesystem
93+
file_path = f'/{filename}'
94+
with open(file_path, 'wb') as f:
95+
f.write(file_content) # Write in one go for small files
96+
# Free memory
97+
gc.collect()
98+
# Redirect to file listing
99+
return Response(status_code=302, headers={'Location': '/files'})
100+
except OSError as e:
101+
return Response(f'Error saving file: {e}', status_code=500)
102+
except MemoryError:
103+
return Response('File too large for available memory', status_code=507)
104+
105+
106+
107+
def startit():
108+
app.run(port=80)
109+
# http://192.168.1.115:5000
110+
111+
112+
import _thread
113+
_thread.start_new_thread(startit, ())
114+

draft_code/qmi8658_reworked.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import struct
2+
import time
3+
4+
from machine import I2C
5+
6+
7+
# Sensor constants
8+
_QMI8685_PARTID = const(0x05)
9+
_REG_PARTID = const(0x00)
10+
_REG_REVISION = const(0x01)
11+
12+
_REG_CTRL1 = const(0x02) # Serial interface and sensor enable
13+
_REG_CTRL2 = const(0x03) # Accelerometer settings
14+
_REG_CTRL3 = const(0x04) # Gyroscope settings
15+
_REG_CTRL4 = const(0x05) # Magnetomer settings (support not implemented in this driver yet)
16+
_REG_CTRL5 = const(0x06) # Sensor data processing settings
17+
_REG_CTRL6 = const(0x07) # Attitude Engine ODR and Motion on Demand
18+
_REG_CTRL7 = const(0x08) # Enable Sensors and Configure Data Reads
19+
20+
_REG_TEMP = const(0x33) # Temperature sensor.
21+
22+
_REG_AX_L = const(0x35) # Read accelerometer
23+
_REG_AX_H = const(0x36)
24+
_REG_AY_L = const(0x37)
25+
_REG_AY_H = const(0x38)
26+
_REG_AZ_L = const(0x39)
27+
_REG_AZ_H = const(0x3A)
28+
29+
_REG_GX_L = const(0x3B) # read gyro
30+
_REG_GX_H = const(0x3C)
31+
_REG_GY_L = const(0x3D)
32+
_REG_GY_H = const(0x3E)
33+
_REG_GZ_L = const(0x3F)
34+
_REG_GZ_H = const(0x40)
35+
36+
_QMI8658_I2CADDR_DEFAULT = const(0X6B)
37+
38+
39+
_ACCELSCALE_RANGE_2G = const(0b00)
40+
_ACCELSCALE_RANGE_4G = const(0b01)
41+
_ACCELSCALE_RANGE_8G = const(0b10)
42+
_ACCELSCALE_RANGE_16G = const(0b11)
43+
44+
_GYROSCALE_RANGE_16DPS = const(0b000)
45+
_GYROSCALE_RANGE_32DPS = const(0b001)
46+
_GYROSCALE_RANGE_64DPS = const(0b010)
47+
_GYROSCALE_RANGE_128DPS = const(0b011)
48+
_GYROSCALE_RANGE_256DPS = const(0b100)
49+
_GYROSCALE_RANGE_512DPS = const(0b101)
50+
_GYROSCALE_RANGE_1024DPS = const(0b110)
51+
_GYROSCALE_RANGE_2048DPS = const(0b111)
52+
53+
_ODR_8000HZ = const(0b0000)
54+
_ODR_4000HZ = const(0b0001)
55+
_ODR_2000HZ = const(0b0010)
56+
_ODR_1000HZ = const(0b0011)
57+
_ODR_500HZ = const(0b0100)
58+
_ODR_250HZ = const(0b0101)
59+
_ODR_125HZ = const(0b0110)
60+
_ODR_62_5HZ = const(0b0111)
61+
62+
63+
class QMI8658:
64+
global _QMI8658_I2CADDR_DEFAULT
65+
def __init__(self,i2c_bus: I2C,address: int = _QMI8658_I2CADDR_DEFAULT,accel_scale: int = _ACCELSCALE_RANGE_8G,gyro_scale: int = _GYROSCALE_RANGE_256DPS):
66+
self.i2c = i2c_bus
67+
self.address = address
68+
# Verify sensor part ID
69+
if self._read_u8(_REG_PARTID) != _QMI8685_PARTID:
70+
raise AttributeError("Cannot find a QMI8658")
71+
# Setup initial configuration
72+
self._configure_sensor(accel_scale, gyro_scale)
73+
# Configure scales/divisors for the driver
74+
self.acc_scale_divisor = {
75+
_ACCELSCALE_RANGE_2G: 1 << 14,
76+
_ACCELSCALE_RANGE_4G: 1 << 13,
77+
_ACCELSCALE_RANGE_8G: 1 << 12,
78+
_ACCELSCALE_RANGE_16G: 1 << 11,
79+
}[accel_scale]
80+
self.gyro_scale_divisor = {
81+
_GYROSCALE_RANGE_16DPS: 2048,
82+
_GYROSCALE_RANGE_32DPS: 1024,
83+
_GYROSCALE_RANGE_64DPS: 512,
84+
_GYROSCALE_RANGE_128DPS: 256,
85+
_GYROSCALE_RANGE_256DPS: 128,
86+
_GYROSCALE_RANGE_512DPS: 64,
87+
_GYROSCALE_RANGE_1024DPS: 32,
88+
_GYROSCALE_RANGE_2048DPS: 16,
89+
}[gyro_scale]
90+
91+
92+
def _configure_sensor(self, accel_scale: int, gyro_scale: int):
93+
# Initialize accelerometer and gyroscope settings
94+
self._write_u8(_REG_CTRL1, 0x60) # Set SPI auto increment and big endian (Ctrl 1)
95+
self._write_u8(_REG_CTRL2, (accel_scale << 4) | _ODR_1000HZ) # Accel Config
96+
self._write_u8(_REG_CTRL3, (gyro_scale << 4) | _ODR_1000HZ) # Gyro Config
97+
self._write_u8(_REG_CTRL5, 0x01) # Low-pass filter enable
98+
self._write_u8(_REG_CTRL7, 0x03) # Enable accel and gyro
99+
time.sleep_ms(100)
100+
101+
102+
# Helper functions for register operations
103+
def _read_u8(self, reg:int) -> int:
104+
return self.i2c.readfrom_mem(self.address, reg, 1)[0]
105+
106+
def _read_xyz(self, reg:int) -> tuple[int, int, int]:
107+
data = self.i2c.readfrom_mem(self.address, reg, 6)
108+
return struct.unpack('<hhh', data)
109+
110+
def _write_u8(self, reg: int, value: int):
111+
self.i2c.writeto_mem(self.address, reg, bytes([value]))
112+
113+
114+
@property
115+
def temperature(self) -> float:
116+
"""Get the device temperature."""
117+
temp_raw = self._read_u8(_REG_TEMP)
118+
return temp_raw / 256
119+
120+
@property
121+
def acceleration(self) -> tuple[float, float, float]:
122+
"""Get current acceleration reading."""
123+
raw_accel = self._read_xyz(_REG_AX_L)
124+
return tuple(val / self.acc_scale_divisor for val in raw_accel)
125+
126+
@property
127+
def gyro(self) -> tuple[float, float, float]:
128+
"""Get current gyroscope reading."""
129+
raw_gyro = self._read_xyz(_REG_GX_L)
130+
return tuple(val / self.gyro_scale_divisor for val in raw_gyro)
131+
132+
133+
import machine
134+
sensor = QMI8658(I2C(0, sda=machine.Pin(48), scl=machine.Pin(47)))
135+
while True:
136+
print(f"""
137+
QMI8685
138+
{sensor.temperature=}
139+
{sensor.acceleration=}
140+
{sensor.gyro=}
141+
""")
142+
time.sleep(1)

0 commit comments

Comments
 (0)