Initial commit
authorBenjamin Braatz <bb@bbraatz.eu>
Thu, 29 Jul 2021 11:12:56 +0000 (13:12 +0200)
committerBenjamin Braatz <bb@bbraatz.eu>
Thu, 29 Jul 2021 11:12:56 +0000 (13:12 +0200)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
controlpi_plugins/camera.py [new file with mode: 0644]
doc/index.md [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3b0f532
--- /dev/null
@@ -0,0 +1,4 @@
+__pycache__/
+dist/
+controlpi_camera.egg-info/
+venv/
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..ebb8ac1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2021 Graph-IT GmbH
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..9cfe9e7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# ControlPi Plugin for Pi Camera Module
+This distribution package contains a plugin for the
+[ControlPi system](https://docs.graph-it.com/graphit/controlpi), that
+uses a connected Pi camera module to create snapshots at configurable
+intervals and announce them on the message bus.
+
+Documentation (in German) can be found at [doc/index.md](doc/index.md) in
+the source repository and at
+[https://docs.graph-it.com/graphit/controlpi-camera/](https://docs.graph-it.com/graphit/controlpi-camera/).
+Code documentation (in English) including doctests is contained in the
+source files.
+An API documentation generated by pdoc3 can be found at
+[doc/controlpi_plugins/index.html](doc/controlpi_plugins/index.html) in the source
+repository and at
+[https://docs.graph-it.com/graphit/controlpi-camera/controlpi_plugins/](https://docs.graph-it.com/graphit/controlpi-camera/controlpi_plugins/).
diff --git a/controlpi_plugins/camera.py b/controlpi_plugins/camera.py
new file mode 100644 (file)
index 0000000..68339ae
--- /dev/null
@@ -0,0 +1,93 @@
+import aiofiles
+import asyncio
+import collections
+import io
+import os
+import picamera
+
+from controlpi import BasePlugin, Message, MessageTemplate
+
+
+class Camera(BasePlugin):
+    CONF_SCHEMA = {'properties':
+                   {'pause': {'type': 'number'},
+                    'keep': {'type': 'integer'},
+                    'path': {'type': 'string'},
+                    'resolution': {'type': 'string'},
+                    'iso': {'type': 'integer'}},
+                   'required': ['pause', 'keep', 'path']}
+
+    async def _receive(self, message: Message) -> None:
+        if message['command'] = 'get image':
+            if len(self._images) > 0:
+                await self.bus.send(Message(self.name,
+                                            {'image': self._images[0]}))
+        elif message['command'] = 'start capture':
+            self._capture = True
+        elif message['command'] = 'stop capture':
+            self._capture = False
+
+    def process_conf(self) -> None:
+        """Register plugin as bus client."""
+        self._images = collections.deque()
+        self._capture = False
+        sends = [MessageTemplate({'event': {'const': 'new image'},
+                                  'image': {'type': 'string'}}),
+                 MessageTemplate({'image': {'type': 'string'}})]
+        receives = [MessageTemplate({'target': {'const': self.name},
+                                     'command': {'const': 'get image'}}),
+                    MessageTemplate({'target': {'const': self.name},
+                                     'command': {'const': 'start capture'}}),
+                    MessageTemplate({'target': {'const': self.name},
+                                     'command': {'const': 'stop capture'}})]
+        self.bus.register(self.name, 'Camera', sends, receives, self._receive)
+
+    async def run(self) -> None:
+        """Run camera."""
+        camera = picamera.PiCamera()
+        stream = io.BytesIO()
+        camera.resolution = '1024x768'
+        if 'resolution' in self.conf:
+            camera.resolution = self.conf['resolution']
+        camera.iso = 100
+        if 'iso' in self.conf:
+            camera.iso = self.conf['iso']
+        try:
+            while True:
+                if self._capture:
+                    camera.exposure_mode = 'auto'
+                    camera.awb_mode = 'auto'
+                    camera.start_preview()
+                    # Wait for auto adjustment and fix settings:
+                    await asyncio.sleep(2)
+                    camera.shutter_speed = camera.exposure_speed
+                    camera.exposure_mode = 'off'
+                    gains = camera.awb_gains
+                    camera.awb_mode = 'off'
+                    camera.awb_gains = gains
+                    while self._capture:
+                        while len(self._images) >= self.conf['keep']:
+                            filename = self._images.popleft()
+                            filepath = os.path.join(self.conf['path'],
+                                                    filename)
+                            await aiofiles.os.remove(filepath)
+                        camera.capture(stream, 'jpeg')
+                        filename = (datetime.datetime.utcnow()
+                                    .strftime('%Y%m%d%H%M%S%f') + '.jpg')
+                        filepath = os.path.join(self.conf['path'], filename)
+                        async with aiofiles.open(filepath, 'wb') as f:
+                            await f.write(stream.getvalue())
+                        self._images.append(filename)
+                        await self.bus.send(Message(self.name,
+                                                    {'event': 'new image',
+                                                     'image': filename}))
+                    camera.stop_preview()
+                await asyncio.sleep(2)
+        except asyncio.CancelledError:
+            camera.stop_preview()
+            while len(self._images) > 0:
+                filename = self._images.popleft()
+                filepath = os.path.join(self.conf['path'],
+                                        filename)
+                await aiofiles.os.remove(filepath)
+            raise
diff --git a/doc/index.md b/doc/index.md
new file mode 100644 (file)
index 0000000..27d3fe7
--- /dev/null
@@ -0,0 +1,27 @@
+# ControlPi-Plugin für Pi-Camera-Modul
+Dieses Paket enthält ein Plugin für das ControlPi-System, mit dem
+Einzelbilder eines angeschlossenen Pi-Camera-Modul regelmäßig in einem
+gegegebenen Pfad abgelegt werden können.
+
+## Installation
+Eine ausführliche Dokumentation ist in der Dokumentation der
+[ControlPi-Infrastruktur](https://docs.graph-it.com/graphit/controlpi) zu
+finden.
+
+Der Code dieses Plugins kann mit git geclonet werden:
+```sh
+$ git clone git://git.graph-it.com/graphit/controlpi-camera.git
+```
+(Falls Zugang zu diesem Server per SSH besteht und Änderungen gepusht
+werden sollen, sollte stattdessen die SSH-URL benutzt werden.)
+
+Dann kann es editierbar in ein virtuelles Environment installiert werden:
+```sh
+(venv)$ pip install --editable <Pfad zum Code-Repository>
+```
+
+Auf dem Raspberry Pi (oder wenn keine Code-Änderungen gewünscht sind) kann
+es auch direkt, ohne einen git-Clone installiert werden:
+```sh
+(venv)$ pip install git+git://git.graph-it.com/graphit/controlpi-camera.git
+```
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..8dfac7e
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,26 @@
+import setuptools
+
+with open("README.md", "r") as readme_file:
+    long_description = readme_file.read()
+
+setuptools.setup(
+    name="controlpi-camera",
+    version="0.1.0",
+    author="Graph-IT GmbH",
+    author_email="info@graph-it.com",
+    description="ControlPi Plugin for Pi Camera",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="http://docs.graph-it.com/graphit/controlpi-camera",
+    packages=["controlpi_plugins"],
+    install_requires=[
+        "aiofiles",
+        "picamera",
+        "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git@master",
+    ],
+    classifiers=[
+        "Programming Language :: Python",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+    ],
+)