Skip to content

Commit

Permalink
Merge pull request #3 from JinneGeelen/feature/v2
Browse files Browse the repository at this point in the history
Merge feature/v2
  • Loading branch information
JinneGeelen authored Aug 8, 2022
2 parents abaea5a + c6febec commit 8b925cc
Show file tree
Hide file tree
Showing 53 changed files with 589 additions and 16,730 deletions.
File renamed without changes.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ui/dist
ui/node_modules
21 changes: 21 additions & 0 deletions Installation_CameraModules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Installation Camera Modules

1. Download the [custom camera image](4TU?) file to build the camera module operating system on the SD cart.
Note! The camera modules are installed one-by-one. Finish all steps for camA before you continue to install camB.
2. Add the custom image to one of the SD carts with [Raspberry Pi Imager](https://www.raspberrypi.com/software/)
3. Connect power supply and insert the SD cart into the Pi
4. Change the hostname from 'cameraX' to the preferred incremental name in order of installation.
5. Check/Choose/Fix the IP address of the module (TBD)
6. Choose prefererred username and password for your controller
1. Enter `sudo raspi-config` in a terminal window
2. Select `Change user password`
The default user on Raspberry Pi OS is `pi` with the password `raspberry`. You can change that here.
3. Set the visible name for this Pi on a network
4. Add Remote Access
1. Enter `sudo raspi-config` in a terminal window
2. Select Interfacing Options
3. Navigate to and select SSH
4. Choose `Yes`
5. Select `Ok`
6. Choose `Finish`
5. Enable/disable the CSI camera interface.
23 changes: 23 additions & 0 deletions Installation_Controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Installation Controller

1. Download the [custom controller image](4TU?) file to build the controller operating system on the SD cart.
2. Add the custom image to one of the SD carts with [Raspberry Pi Imager](https://www.raspberrypi.com/software/)
3. Connect power supply and insert the SD cart into the Pi
4. Choose prefererred username and password for your controller
1. Enter `sudo raspi-config` in a terminal window
2. Select `Change user password`
The default user on Raspberry Pi OS is `pi` with the password `raspberry`. You can change that here.
3. Set the visible name for this Pi on a network
3. Add WiFi settings
1. Enter `sudo raspi-config` in a terminal window
2. Select Networking Options
3. Navigate to and select WiFi Settings
4. Fill in the credentials of your WiFi network
4. Add Remote Access
1. Enter `sudo raspi-config` in a terminal window
2. Select Interfacing Options
3. Navigate to and select SSH
4. Choose `Yes`
5. Select `Ok`
6. Choose `Finish`
5. Enable/disable the CSI camera interface.
12 changes: 12 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.9-buster

RUN apt-get update && apt-get install -y gcc libpq-dev

WORKDIR /app

COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt

COPY . /app

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
21 changes: 8 additions & 13 deletions controller/app.py → api/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import databases
import uvicorn
import logging

from starlette.applications import Starlette
Expand All @@ -12,7 +11,7 @@
from api import cameras, diagnostic_results, diagnostics, participants, recordings, studies
from db import get_db, init_tables
from controllers import camera_controller
from config import DEBUG, DATABASE_URL, PORT
from config import DEBUG, DATABASE_URL


# Set up logging level globally
Expand All @@ -30,17 +29,17 @@
allow_headers=['*'])

# Mount all the different API's
app.mount('/v1/cameras', cameras)
app.mount('/v1/diagnostic_results', diagnostic_results)
app.mount('/v1/diagnostics', diagnostics)
app.mount('/v1/participants', participants)
app.mount('/v1/recordings', recordings)
app.mount('/v1/studies', studies)
app.mount('/api/cameras', cameras)
app.mount('/api/diagnostic_results', diagnostic_results)
app.mount('/api/diagnostics', diagnostics)
app.mount('/api/participants', participants)
app.mount('/api/recordings', recordings)
app.mount('/api/studies', studies)


# Set up OpenAPI schema
schemas = SchemaGenerator(
{"openapi": "3.0.0", "info": {"title": "PiCam Controller API", "version": "1.0"}}
{"openapi": "3.0.0", "info": {"title": "ML-MoCap Controller API", "version": "1.0"}}
)


Expand All @@ -61,7 +60,3 @@ async def startup():
@app.on_event("shutdown")
async def shutdown():
await db.disconnect()

# Start the application
if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=PORT)
File renamed without changes.
4 changes: 1 addition & 3 deletions controller/config/config.py → api/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,5 @@
config = Config('.venv')

DEBUG = config('DEBUG', cast=bool, default=False)
PORT = config('PORT', cast=int)
LOCAL_STORAGE_PATH = config('LOCAL_STORAGE_PATH', cast=str)
REMOTE_STORAGE_PATH = config('REMOTE_STORAGE_PATH', cast=str)
STORAGE_PATH = config('STORAGE_PATH', cast=str)
DATABASE_URL = config('DATABASE_URL', cast=DatabaseURL)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from rx.subject import Subject

from db import get_db, metadata
from config import LOCAL_STORAGE_PATH, REMOTE_STORAGE_PATH
from config import STORAGE_PATH

from controllers.controller import Controller
from controllers.participants import participant_controller
Expand All @@ -21,13 +21,8 @@

from gpiozero import LED

#trigger for TMSi Porti, pin1 = 3.3V (GND = pin6)
trigger = LED("GPIO21") #LED("BOARD40")
led_red = LED("GPIO5") #LED("BOARD29")
led_yellow = LED("GPIO6") #LED("BOARD31")
led_green = LED("GPIO13") #LED("BOARD33")
led_blue = LED("GPIO19") #LED("BOARD35")
led_white = LED("GPIO26") #LED("BOARD")
# trigger for TMSi Porti, pin1 = 3.3V (GND = pin6)
trigger = LED("GPIO21") # LED("BOARD40")

logging.basicConfig(level=logging.DEBUG,
format='%(relativeCreated)6d %(threadName)s %(message)s')
Expand Down Expand Up @@ -91,38 +86,33 @@ async def get_recording_metadata(self, recording):
async def get_recording_path(self, recording):
participant = await participant_controller.get(recording.get('participant_id'))
study = await studies_controller.get(participant.get('study_id'))
path = '{}_{}_{}'.format(study.get('name'),
path = '{}/{}/{}'.format(study.get('name'),
participant.get('number'),
recording.get('name'))

return path.replace(' ', '_').replace(':', '_').replace('-', '_')

async def get_local_storage_path(self, recording):
async def get_storage_path(self, recording):
recording_path = await self.get_recording_path(recording)
return '{}/{}'.format(LOCAL_STORAGE_PATH, recording_path)

async def get_remote_storage_path(self, recording):
recording_path = await self.get_recording_path(recording)
return '{}/{}'.format(REMOTE_STORAGE_PATH, recording_path)
return '{}/{}'.format(STORAGE_PATH, recording_path)

async def get_camera_file_path(self, recording, camera_id):
local_path = await self.get_local_storage_path(recording)
local_path = await self.get_storage_path(recording)

camera = await camera_controller.get(camera_id)
return '{}_{}.mp4'.format(local_path, camera.get('name', camera_id).replace(' ', '_'))
return '{}/{}.mp4'.format(local_path, camera.get('name', camera_id).replace(' ', '_'))

#new start
async def sendtrigger(self, recording):
start_time = recording.get('start_time')
def send_trigger(self, recording):
start_time = datetime.fromisoformat(recording.get('start_time'))
current_time = datetime.now().astimezone()

while current_time < start_time and self.recording:
while current_time < start_time:
time.sleep(0.001)
current_time = datetime.now().astimezone()

trigger.on()
time.sleep(2)
trigger.off()
#new end

async def start(self, recording_id):
async with self.db.transaction():
Expand All @@ -145,11 +135,11 @@ async def start(self, recording_id):
recording['cameras_recorded'] = cameras_recorded
recording = await self.update(recording)

#send trigger to TMSi Porti
thread = threading.Thread(
target=self.sendtrigger, args=(recording), daemon=True)
# send trigger to TMSi Porti
thread = threading.Thread(target=self.send_trigger,
args=[recording], daemon=True)
thread.start()

return recording

async def stop(self, recording_id):
Expand Down Expand Up @@ -196,21 +186,23 @@ async def process(self, recording_id):
if not recording:
return

local_storage_path = await self.get_local_storage_path(recording)
remote_storage_path = await self.get_remote_storage_path(recording)
storage_path = await self.get_storage_path(recording)

if not os.path.exists(remote_storage_path):
os.makedirs(remote_storage_path)
if not os.path.exists(storage_path):
os.makedirs(storage_path)

recording_metadata = await self.get_recording_metadata(recording)
metadata_path = '{}/metadata.json'.format(remote_storage_path)
metadata_path = '{}/metadata.json'.format(storage_path)

with open(metadata_path, 'w') as file:
file.write(json.dumps(recording_metadata, indent=2))

cameras_processing = await camera_controller.send_command({
'event': 'process_recording',
'data': recording,
'data': {
'recording': recording,
'storage_path': storage_path,
},
})

recording['cameras_processing'] = cameras_processing
Expand All @@ -222,16 +214,6 @@ async def process(self, recording_id):

async def processed(self, recording_id, camera_id):
recording = await self.get(recording_id)
camera = await camera_controller.get(camera_id)

source = await self.get_camera_file_path(recording, camera_id)
base_path = await self.get_remote_storage_path(recording)
dest = '{}/{}.mp4'.format(base_path, camera.get('name', camera_id))

thread = threading.Thread(
target=self.upload, args=(source, dest), daemon=True)
thread.start()

async with self.db.transaction():
await self.db.execute('LOCK TABLE recordings IN SHARE ROW EXCLUSIVE MODE')

Expand All @@ -248,11 +230,6 @@ async def processed(self, recording_id, camera_id):

return recording

def upload(self, source, dest):
logger.info('Upload {} to {}'.format(source, dest))
shutil.move(source, dest)
logger.info('Done uploading {}'.format(dest))

async def get_all(self):
query = recordings.select()
results = await self.db.fetch_all(query)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
RPi.GPIO
databases
uvicorn[standard]
starlette
sqlalchemy
asyncpg
pyyaml
rx
psycopg2
shortuuid
gpiozero

14 changes: 11 additions & 3 deletions camera/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
FROM raspbian/stretch
FROM balenalib/raspberry-pi-python:3.7-buster

COPY camera camera
WORKDIR /app

CMD [ "./camera" ]
ENV READTHEDOCS=True

COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
RUN apt update && apt install -y gpac --fix-missing

COPY . /app

CMD ["python3", "app.py"]
16 changes: 0 additions & 16 deletions camera/Pipfile

This file was deleted.

Loading

0 comments on commit 8b925cc

Please sign in to comment.