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.
Files changed (27) hide show
  1. {meltingplot_rpi_camera-0.0.2/meltingplot.rpi_camera.egg-info → meltingplot_rpi_camera-0.0.3}/PKG-INFO +3 -2
  2. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/README.rst +1 -1
  3. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/_version.py +3 -3
  4. meltingplot_rpi_camera-0.0.3/meltingplot/rpi_camera/cli/install.py +73 -0
  5. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/server.py +50 -6
  6. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3/meltingplot.rpi_camera.egg-info}/PKG-INFO +3 -2
  7. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/SOURCES.txt +1 -0
  8. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/requires.txt +1 -0
  9. meltingplot_rpi_camera-0.0.3/reboot_on_wifi_disconnect.sh +65 -0
  10. meltingplot_rpi_camera-0.0.3/requirements.txt +2 -0
  11. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/setup.py +1 -0
  12. meltingplot_rpi_camera-0.0.2/meltingplot/rpi_camera/cli/install.py +0 -55
  13. meltingplot_rpi_camera-0.0.2/requirements.txt +0 -1
  14. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/LICENSE +0 -0
  15. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/MANIFEST.in +0 -0
  16. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/__init__.py +0 -0
  17. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__init__.py +0 -0
  18. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/__main__.py +0 -0
  19. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot/rpi_camera/cli/__init__.py +0 -0
  20. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/dependency_links.txt +0 -0
  21. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/entry_points.txt +0 -0
  22. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/not-zip-safe +0 -0
  23. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/meltingplot.rpi_camera.egg-info/top_level.txt +0 -0
  24. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/requirements_test.txt +0 -0
  25. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/rpi-camera.service +0 -0
  26. {meltingplot_rpi_camera-0.0.2 → meltingplot_rpi_camera-0.0.3}/setup.cfg +0 -0
  27. {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.2
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
- sudo rpi-camera install
263
+ rpi-camera install
263
264
 
264
265
  Usage
265
266
  -----
@@ -29,7 +29,7 @@ To install the required dependencies, run:
29
29
  python3 -m venv --system-site-packages venv
30
30
  source venv/bin/activate
31
31
  pip install meltingplot.rpi_camera
32
- sudo rpi-camera install
32
+ rpi-camera install
33
33
 
34
34
  Usage
35
35
  -----
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-11-27T21:05:27+0100",
11
+ "date": "2024-12-02T11:25:54+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "9922b35613936eaed383a1153f17e24c4f7b98c3",
15
- "version": "0.0.2"
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.')
@@ -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
- """Initialize the streaming output with a frame buffer and condition."""
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
- picam2 = Picamera2()
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({"AfMode": controls.AfModeEnum.Continuous})
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.2
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
- sudo rpi-camera install
263
+ rpi-camera install
263
264
 
264
265
  Usage
265
266
  -----
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  MANIFEST.in
3
3
  README.rst
4
+ reboot_on_wifi_disconnect.sh
4
5
  requirements.txt
5
6
  requirements_test.txt
6
7
  rpi-camera.service
@@ -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
@@ -0,0 +1,2 @@
1
+ click
2
+ piexif
@@ -50,6 +50,7 @@ setup(
50
50
  data_files=[
51
51
  ('', [
52
52
  'rpi-camera.service',
53
+ 'reboot_on_wifi_disconnect.sh',
53
54
  ]),
54
55
  ],
55
56
  )
@@ -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