meltingplot.rpi-camera 0.0.2__tar.gz → 0.0.3__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.
- {meltingplot_rpi_camera-0.0.2/meltingplot.rpi_camera.egg-info → meltingplot_rpi_camera-0.0.3}/PKG-INFO +3 -2
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/README.rst +1 -1
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/_version.py +3 -3
- meltingplot_rpi_camera-0.0.3/meltingplot/rpi_camera/cli/install.py +73 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/server.py +50 -6
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3/meltingplot.rpi_camera.egg-info}/PKG-INFO +3 -2
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/SOURCES.txt +1 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/requires.txt +1 -0
- meltingplot_rpi_camera-0.0.3/reboot_on_wifi_disconnect.sh +65 -0
- meltingplot_rpi_camera-0.0.3/requirements.txt +2 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/setup.py +1 -0
- meltingplot_rpi_camera-0.0.2/meltingplot/rpi_camera/cli/install.py +0 -55
- meltingplot_rpi_camera-0.0.2/requirements.txt +0 -1
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/LICENSE +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/MANIFEST.in +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/__init__.py +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__init__.py +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__main__.py +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/cli/__init__.py +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/dependency_links.txt +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/entry_points.txt +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/not-zip-safe +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/top_level.txt +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/requirements_test.txt +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/rpi-camera.service +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/setup.cfg +0 -0
- {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/versioneer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: meltingplot.rpi_camera
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: RPi Camera MJPEG Streamer.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author: Tim Schneider
|
|
@@ -210,6 +210,7 @@ License: Apache License
|
|
|
210
210
|
Description-Content-Type: text/x-rst
|
|
211
211
|
License-File: LICENSE
|
|
212
212
|
Requires-Dist: click
|
|
213
|
+
Requires-Dist: piexif
|
|
213
214
|
Provides-Extra: test
|
|
214
215
|
Requires-Dist: pytest-cov; extra == "test"
|
|
215
216
|
Requires-Dist: pytest-mock; extra == "test"
|
|
@@ -259,7 +260,7 @@ To install the required dependencies, run:
|
|
|
259
260
|
python3 -m venv --system-site-packages venv
|
|
260
261
|
source venv/bin/activate
|
|
261
262
|
pip install meltingplot.rpi_camera
|
|
262
|
-
|
|
263
|
+
rpi-camera install
|
|
263
264
|
|
|
264
265
|
Usage
|
|
265
266
|
-----
|
{meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/_version.py
RENAMED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2024-
|
|
11
|
+
"date": "2024-12-02T11:25:54+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.0.
|
|
14
|
+
"full-revisionid": "693a3d12e8aab7ea9139e0cd97f1eadd687edc1c",
|
|
15
|
+
"version": "0.0.3"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Install the RPi Camera as a systemd service."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command()
|
|
7
|
+
def install():
|
|
8
|
+
"""Install the RPi Camera as a systemd service."""
|
|
9
|
+
import os
|
|
10
|
+
import getpass
|
|
11
|
+
import grp
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
|
|
16
|
+
# Get the path of the service file
|
|
17
|
+
rpi_camera_service_file = os.path.join(sys.prefix, 'rpi-camera.service')
|
|
18
|
+
|
|
19
|
+
service_content = None
|
|
20
|
+
|
|
21
|
+
# Read the content of the service file
|
|
22
|
+
with open(rpi_camera_service_file, 'r') as file:
|
|
23
|
+
service_content = file.read()
|
|
24
|
+
|
|
25
|
+
# Replace User and Group with the current user and group
|
|
26
|
+
current_user = getpass.getuser()
|
|
27
|
+
current_group = grp.getgrgid(os.getgid()).gr_name
|
|
28
|
+
service_content = service_content.replace('User=pi', f'User={current_user}', 1)
|
|
29
|
+
service_content = service_content.replace('Group=pi', f'Group={current_group}', 1)
|
|
30
|
+
|
|
31
|
+
click.echo(f"Installing the service as user/group: {current_user}/{current_group}")
|
|
32
|
+
|
|
33
|
+
# Save the modified content to a temp file
|
|
34
|
+
with tempfile.NamedTemporaryFile('wt+') as tmp_file:
|
|
35
|
+
tmp_file.write(service_content)
|
|
36
|
+
tmp_file.flush()
|
|
37
|
+
|
|
38
|
+
# Copy the service file to /etc/systemd/system
|
|
39
|
+
subprocess.check_output(['sudo', 'cp', tmp_file.name, '/etc/systemd/system/rpi-camera.service'])
|
|
40
|
+
|
|
41
|
+
executable_file = os.path.join(sys.exec_prefix, 'bin/rpi-camera')
|
|
42
|
+
|
|
43
|
+
# Make the rpi-camera command available outside the venv
|
|
44
|
+
subprocess.check_output(['sudo', 'ln', '-sf', executable_file, '/usr/local/bin/rpi-camera'])
|
|
45
|
+
|
|
46
|
+
click.echo('Configuring static IP for wlan0 using nmcli to 10.42.0.3')
|
|
47
|
+
|
|
48
|
+
# Set the static IP for wlan0 using nmcli
|
|
49
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'mod', 'preconfigured', 'ipv4.addresses', '10.42.0.3/24'])
|
|
50
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'mod', 'preconfigured', 'ipv4.gateway', '10.42.0.1'])
|
|
51
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'mod', 'preconfigured', 'ipv4.dns', '10.42.0.1'])
|
|
52
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'mod', 'preconfigured', 'ipv4.method', 'manual'])
|
|
53
|
+
|
|
54
|
+
# Bring the connection down and up to apply changes
|
|
55
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'down', 'preconfigured'])
|
|
56
|
+
subprocess.check_output(['sudo', 'nmcli', 'con', 'up', 'preconfigured'])
|
|
57
|
+
|
|
58
|
+
click.echo('Install reboot on wifi disconnect service')
|
|
59
|
+
wifi_script_file = os.path.join(sys.prefix, 'reboot_on_wifi_disconnect.sh')
|
|
60
|
+
subprocess.check_output(['sudo', 'cp', '-f', wifi_script_file, '/usr/local/bin/reboot_on_wifi_disconnect.sh'])
|
|
61
|
+
subprocess.check_output(['sudo', 'chmod', '+x', '/usr/local/bin/reboot_on_wifi_disconnect.sh'])
|
|
62
|
+
subprocess.check_output(['sudo', '/usr/local/bin/reboot_on_wifi_disconnect.sh', 'install'])
|
|
63
|
+
|
|
64
|
+
# Reload the systemd daemon
|
|
65
|
+
subprocess.check_output(['sudo', 'systemctl', 'daemon-reload'])
|
|
66
|
+
|
|
67
|
+
# Enable the service
|
|
68
|
+
subprocess.check_output(['sudo', 'systemctl', 'enable', 'rpi-camera'])
|
|
69
|
+
|
|
70
|
+
# Start the service
|
|
71
|
+
subprocess.check_output(['sudo', 'systemctl', 'start', 'rpi-camera'])
|
|
72
|
+
|
|
73
|
+
click.echo('The RPi Camera has been installed as a systemd service.')
|
{meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/server.py
RENAMED
|
@@ -43,6 +43,8 @@ from picamera2 import Picamera2
|
|
|
43
43
|
from picamera2.encoders import MJPEGEncoder
|
|
44
44
|
from picamera2.outputs import FileOutput
|
|
45
45
|
|
|
46
|
+
import piexif
|
|
47
|
+
|
|
46
48
|
|
|
47
49
|
PAGE = """\
|
|
48
50
|
<html>
|
|
@@ -69,15 +71,49 @@ PAGE = """\
|
|
|
69
71
|
class StreamingOutput(io.BufferedIOBase):
|
|
70
72
|
"""A class that buffers the video frames and notifies waiting threads when a new frame is available."""
|
|
71
73
|
|
|
72
|
-
def __init__(self):
|
|
73
|
-
"""
|
|
74
|
+
def __init__(self, rotation: int = 0):
|
|
75
|
+
"""
|
|
76
|
+
Initialize the streaming output with a frame buffer and condition.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
rotation (int): The rotation angle for the JPEG image. Must be one of
|
|
80
|
+
[0, 90, 180, 270]. Default is 0.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: If the rotation value is not one of [0, 90, 180, 270].
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
frame (None): Placeholder for the frame buffer.
|
|
87
|
+
condition (Condition): Condition variable for thread synchronization.
|
|
88
|
+
"""
|
|
74
89
|
self.frame = None
|
|
75
90
|
self.condition = Condition()
|
|
76
91
|
|
|
92
|
+
# Set the orientation of the JPEG image based on the rotation value
|
|
93
|
+
# more info: http://sylvana.net/jpegcrop/exif_orientation.html
|
|
94
|
+
if rotation == 0:
|
|
95
|
+
orientation = 1
|
|
96
|
+
elif rotation == 90:
|
|
97
|
+
orientation = 6
|
|
98
|
+
elif rotation == 180:
|
|
99
|
+
orientation = 3
|
|
100
|
+
elif rotation == 270:
|
|
101
|
+
orientation = 8
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError("Invalid rotation value")
|
|
104
|
+
|
|
105
|
+
exif_data = piexif.dump({
|
|
106
|
+
"0th": {
|
|
107
|
+
piexif.ImageIFD.Orientation: orientation,
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
jpeg_app_len = len(exif_data) + 2
|
|
111
|
+
self._jpeg_app1 = b"\xff\xe1" + (jpeg_app_len).to_bytes(2, byteorder="big") + exif_data
|
|
112
|
+
|
|
77
113
|
def write(self, buf):
|
|
78
114
|
"""Write the buffer to the stream and notify waiting threads."""
|
|
79
115
|
with self.condition:
|
|
80
|
-
self.frame = buf
|
|
116
|
+
self.frame = buf[:2] + self._jpeg_app1 + buf[2:]
|
|
81
117
|
self.condition.notify_all()
|
|
82
118
|
|
|
83
119
|
|
|
@@ -173,14 +209,22 @@ def start():
|
|
|
173
209
|
Raise:
|
|
174
210
|
Exception: If there is an error during the server execution or camera operation.
|
|
175
211
|
"""
|
|
176
|
-
frame_buffer = StreamingOutput()
|
|
212
|
+
frame_buffer = StreamingOutput(rotation=180)
|
|
177
213
|
HttpHandler.frame_buffer = frame_buffer
|
|
178
214
|
StreamingHandler.frame_buffer = frame_buffer
|
|
179
215
|
|
|
180
|
-
|
|
216
|
+
try:
|
|
217
|
+
picam2 = Picamera2()
|
|
218
|
+
except IndexError as e:
|
|
219
|
+
logging.error("Error initializing the camera: %s - is a RPi camera connected?", str(e))
|
|
220
|
+
raise
|
|
221
|
+
|
|
181
222
|
picam2.configure(picam2.create_video_configuration(main={"size": (1920, 1080)}))
|
|
182
223
|
picam2.start_recording(MJPEGEncoder(), FileOutput(frame_buffer))
|
|
183
|
-
picam2.set_controls({
|
|
224
|
+
picam2.set_controls({
|
|
225
|
+
"AfMode": controls.AfModeEnum.Continuous,
|
|
226
|
+
"FrameRate": 10,
|
|
227
|
+
})
|
|
184
228
|
|
|
185
229
|
try:
|
|
186
230
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: meltingplot.rpi_camera
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: RPi Camera MJPEG Streamer.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author: Tim Schneider
|
|
@@ -210,6 +210,7 @@ License: Apache License
|
|
|
210
210
|
Description-Content-Type: text/x-rst
|
|
211
211
|
License-File: LICENSE
|
|
212
212
|
Requires-Dist: click
|
|
213
|
+
Requires-Dist: piexif
|
|
213
214
|
Provides-Extra: test
|
|
214
215
|
Requires-Dist: pytest-cov; extra == "test"
|
|
215
216
|
Requires-Dist: pytest-mock; extra == "test"
|
|
@@ -259,7 +260,7 @@ To install the required dependencies, run:
|
|
|
259
260
|
python3 -m venv --system-site-packages venv
|
|
260
261
|
source venv/bin/activate
|
|
261
262
|
pip install meltingplot.rpi_camera
|
|
262
|
-
|
|
263
|
+
rpi-camera install
|
|
263
264
|
|
|
264
265
|
Usage
|
|
265
266
|
-----
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Function to check if wlan0 is connected
|
|
4
|
+
check_wlan0_connected() {
|
|
5
|
+
iw dev wlan0 link | grep -q "Connected"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
# Function to check if 10.42.0.1 is reachable
|
|
9
|
+
check_ip_reachable() {
|
|
10
|
+
ping -c 1 10.42.0.1 &> /dev/null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Function to install the systemd service
|
|
14
|
+
install_service() {
|
|
15
|
+
# Create a systemd service file for this script
|
|
16
|
+
cat <<EOF | sudo tee /etc/systemd/system/reboot_on_wifi_disconnect.service
|
|
17
|
+
[Unit]
|
|
18
|
+
Description=Reboot on Lost WiFi
|
|
19
|
+
After=network.target NetworkManager.service
|
|
20
|
+
|
|
21
|
+
[Service]
|
|
22
|
+
ExecStartPre=/bin/sleep 60
|
|
23
|
+
ExecStart=/usr/local/bin/reboot_on_wifi_disconnect.sh
|
|
24
|
+
Restart=always
|
|
25
|
+
User=root
|
|
26
|
+
Type=simple
|
|
27
|
+
|
|
28
|
+
[Install]
|
|
29
|
+
WantedBy=multi-user.target
|
|
30
|
+
EOF
|
|
31
|
+
|
|
32
|
+
# Reload systemd manager configuration
|
|
33
|
+
sudo systemctl daemon-reload
|
|
34
|
+
|
|
35
|
+
# Enable the service to start on boot
|
|
36
|
+
sudo systemctl enable reboot_on_wifi_disconnect.service
|
|
37
|
+
|
|
38
|
+
# Start the service immediately
|
|
39
|
+
sudo systemctl start reboot_on_wifi_disconnect.service
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Main function to monitor WiFi connection
|
|
43
|
+
monitor_wifi() {
|
|
44
|
+
failure_count=0
|
|
45
|
+
while true; do
|
|
46
|
+
# Check connection status every second for 5 seconds
|
|
47
|
+
if ! check_wlan0_connected || ! check_ip_reachable; then
|
|
48
|
+
failure_count=$((failure_count + 1))
|
|
49
|
+
if [ "$failure_count" -ge 3 ]; then
|
|
50
|
+
echo "wlan0 is not connected or 10.42.0.1 is not reachable for 3 consecutive checks. Rebooting..."
|
|
51
|
+
reboot
|
|
52
|
+
fi
|
|
53
|
+
else
|
|
54
|
+
failure_count=0
|
|
55
|
+
fi
|
|
56
|
+
sleep 1
|
|
57
|
+
done
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Check if the script is called with "install" argument
|
|
61
|
+
if [ "$1" == "install" ]; then
|
|
62
|
+
install_service
|
|
63
|
+
else
|
|
64
|
+
monitor_wifi
|
|
65
|
+
fi
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"""Install the RPi Camera as a systemd service."""
|
|
2
|
-
|
|
3
|
-
import click
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@click.command()
|
|
7
|
-
def install():
|
|
8
|
-
"""Install the RPi Camera as a systemd service."""
|
|
9
|
-
import os
|
|
10
|
-
import getpass
|
|
11
|
-
import grp
|
|
12
|
-
import subprocess
|
|
13
|
-
import sys
|
|
14
|
-
import tempfile
|
|
15
|
-
|
|
16
|
-
# Get the path of the service file
|
|
17
|
-
service_file = os.path.join(sys.prefix, 'rpi-camera.service')
|
|
18
|
-
|
|
19
|
-
service_content = None
|
|
20
|
-
|
|
21
|
-
# Read the content of the service file
|
|
22
|
-
with open(service_file, 'r') as file:
|
|
23
|
-
service_content = file.read()
|
|
24
|
-
|
|
25
|
-
# Replace User and Group with the current user and group
|
|
26
|
-
current_user = getpass.getuser()
|
|
27
|
-
current_group = grp.getgrgid(os.getgid()).gr_name
|
|
28
|
-
service_content = service_content.replace('User=pi', f'User={current_user}', 1)
|
|
29
|
-
service_content = service_content.replace('Group=pi', f'Group={current_group}', 1)
|
|
30
|
-
|
|
31
|
-
click.echo(f"Installing the service as user/group: {current_user}/{current_group}")
|
|
32
|
-
|
|
33
|
-
# Save the modified content to a temp file
|
|
34
|
-
with tempfile.NamedTemporaryFile('wt+') as tmp_file:
|
|
35
|
-
tmp_file.write(service_content)
|
|
36
|
-
tmp_file.flush()
|
|
37
|
-
|
|
38
|
-
# Copy the service file to /etc/systemd/system
|
|
39
|
-
subprocess.check_output(['sudo', 'cp', tmp_file.name, '/etc/systemd/system/rpi-camera.service'])
|
|
40
|
-
|
|
41
|
-
# Reload the systemd daemon
|
|
42
|
-
subprocess.run(['sudo', 'systemctl', 'daemon-reload'])
|
|
43
|
-
|
|
44
|
-
# Enable the service
|
|
45
|
-
subprocess.run(['sudo', 'systemctl', 'enable', 'rpi-camera'])
|
|
46
|
-
|
|
47
|
-
# Start the service
|
|
48
|
-
subprocess.run(['sudo', 'systemctl', 'start', 'rpi-camera'])
|
|
49
|
-
|
|
50
|
-
executable_file = os.path.join(sys.prefix, 'bin/rpi-camera')
|
|
51
|
-
|
|
52
|
-
# Make the rpi-camera command available outside the venv
|
|
53
|
-
subprocess.run(['sudo', 'ln', '-sf', executable_file, '/usr/local/bin/rpi-camera'])
|
|
54
|
-
|
|
55
|
-
print('The RPi Camera has been installed as a systemd service.')
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
click
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__init__.py
RENAMED
|
File without changes
|
{meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__main__.py
RENAMED
|
File without changes
|
{meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/cli/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|