├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── docker-build.sh ├── setup.py ├── sql └── dump.sql └── src └── chkcamera ├── __init__.py ├── __main__.py └── core.py /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1.0.0-experimental 2 | FROM latonaio/l4t:latest 3 | 4 | # Definition of a Device & Service 5 | ENV POSITION=Runtime \ 6 | SERVICE=check-multiple-camera-connection \ 7 | AION_HOME=/var/lib/aion 8 | 9 | # Setup Directoties 10 | RUN mkdir -p /${AION_HOME}/$POSITION/$SERVICE 11 | 12 | WORKDIR /${AION_HOME}/$POSITION/$SERVICE 13 | 14 | RUN apt-get update && apt-get install -y \ 15 | v4l-utils \ 16 | && apt-get clean \ 17 | && rm -rf /var/lib/apt/lists/* 18 | 19 | ADD . . 20 | RUN git config --global url."git@bitbucket.org:".insteadOf "https://bitbucket.org/" 21 | RUN --mount=type=secret,id=ssh,target=/root/.ssh/id_rsa ssh-keyscan -t rsa bitbucket.org >> /root/.ssh/known_hosts \ 22 | && pip3 install -U git+ssh://git@bitbucket.org/latonaio/aion-related-python-library.git 23 | RUN python3 setup.py install 24 | 25 | CMD ["python3", "-m", "chkcamera"] 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Latona, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | docker-build: 2 | bash docker-build.sh 3 | 4 | docker-push: 5 | bash docker-build.sh push 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # check-multiple-camera-connection-kube 2 | check-multiple-camera-connection-kubeは、主にエッジコンピューティング環境において、USBで接続されたカメラ情報を検出するマイクロサービスです。 3 | check-multiple-camera-connection-kubeは、USBで接続されている複数のカメラの接続情報を取得し、他マイクロサービスへ配信します。 4 | 配信はkanbanもしくはデータベースを通じて行われます。 5 | *DBを通じた配信は現在非推奨になっております 6 | 7 | 配信されるデータは下記の二種類です。 8 | 9 | - 接続されているデバイスのリスト 10 | - 各デバイスのシリアル番号のリスト 11 | 12 | デバイスのリストは`v4l2-ctl`コマンドを使用して取得されます。 13 | 14 | # 動作環境 15 | check-multiple-camera-connection-kubeはAIONのプラットフォーム上での動作を前提としています。 16 | 使用する際は、事前にAIONの動作環境を用意してください。 17 | - OS: Linux OS 18 | - CPU: ARM/AMD/Intel 19 | - Kubernetes 20 | - AION 21 | 22 | # セットアップ 23 | このリポジトリをクローンし、makeコマンドを用いてDocker container imageのビルドを行ってください。 24 | ``` 25 | $ cd check-multiple-camera-connection-kube 26 | $ make docker-build 27 | ``` 28 | 29 | ## デプロイ on AION 30 | AION上でデプロイする場合、project.yamlに次の設定を追加してください。 31 | ``` 32 | check-multiple-camera-connection: 33 | privileged: yes 34 | startup: yes // aion起動時の立ち上げ設定 35 | always: yes // 常時稼働 36 | env: 37 | MYSQL_USER: {user_name} 38 | MYSQL_PASSWORD: {password} 39 | KANBAN_ADDR: aion-statuskanban:10000 40 | nextService: 41 | default: 42 | - name: XXX //kanbanの監視下にある伝達先マイクロサービス 43 | volumeMountPathList: 44 | - /devices:/dev:Bidirectional 45 | ``` 46 | 47 | # I/O 48 | ### input 49 | なし 50 | 51 | ### output 52 | - device_list: 取得したデバイスリスト 53 | - devices: 各デバイスのシリアル番号のリスト -------------------------------------------------------------------------------- /docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PUSH=$1 4 | DATE="$(date "+%Y%m%d%H%M")" 5 | REPOSITORY_PREFIX="latonaio" 6 | SERVICE_NAME="check-multiple-camera-connection" 7 | 8 | DOCKER_BUILDKIT=1 docker build --no-cache --secret id=ssh,src=$HOME/.ssh/id_rsa --progress=plain -t ${SERVICE_NAME}:"${DATE}" . 9 | # tagging 10 | docker tag ${SERVICE_NAME}:"${DATE}" ${SERVICE_NAME}:latest 11 | docker tag ${SERVICE_NAME}:"${DATE}" ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}" 12 | docker tag ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}" ${REPOSITORY_PREFIX}/${SERVICE_NAME}:latest 13 | 14 | if [[ $PUSH == "push" ]]; then 15 | docker push ${REPOSITORY_PREFIX}/${SERVICE_NAME}:"${DATE}" 16 | docker push ${REPOSITORY_PREFIX}/${SERVICE_NAME}:latest 17 | fi 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) 2019-2020 Latona. All rights reserved. 4 | 5 | from setuptools import setup, find_packages 6 | 7 | setup( 8 | name="check-multiple-camera-connection", 9 | version="0.0.1", 10 | author="Latona", 11 | packages=find_packages("./src"), 12 | package_dir={"": "src"}, 13 | install_requires=[], 14 | tests_require=[] 15 | ) 16 | -------------------------------------------------------------------------------- /sql/dump.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.7.31, for Linux (aarch64) 2 | -- 3 | -- Host: 127.0.0.1 Database: PeripheralDevice 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.5-10.2.32-MariaDB-1:10.2.32+maria~bionic 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `cameras` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `cameras`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `cameras` ( 26 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 27 | `serial` varchar(60) NOT NULL, 28 | `state` tinyint(4) NOT NULL, 29 | `path` varchar(45) DEFAULT NULL, 30 | `timestamp` datetime DEFAULT current_timestamp(), 31 | PRIMARY KEY (`id`), 32 | UNIQUE KEY `id_UNIQUE` (`id`), 33 | UNIQUE KEY `serial_UNIQUE` (`serial`) 34 | ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=latin1; 35 | /*!40101 SET character_set_client = @saved_cs_client */; 36 | 37 | -- 38 | -- Dumping data for table `cameras` 39 | -- 40 | 41 | LOCK TABLES `cameras` WRITE; 42 | /*!40000 ALTER TABLE `cameras` DISABLE KEYS */; 43 | INSERT INTO `cameras` VALUES (1,'046d_C922_Pro_Stream_Webcam_B9CBB83F',1,'/dev/video0','2020-08-12 08:38:05'),(9,'046d_C922_Pro_Stream_Webcam_92CB266F',1,'/dev/video1','2020-08-12 08:38:05'),(10,'046d_C922_Pro_Stream_Webcam_7C4AA21F',0,'','2020-08-12 08:17:00'); 44 | /*!40000 ALTER TABLE `cameras` ENABLE KEYS */; 45 | UNLOCK TABLES; 46 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 47 | 48 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 49 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 50 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 51 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 52 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 53 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 54 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 55 | 56 | -- Dump completed on 2020-08-12 17:38:05 57 | -------------------------------------------------------------------------------- /src/chkcamera/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) 2019-2020 Latona. All rights reserved. 4 | 5 | from .core import main 6 | -------------------------------------------------------------------------------- /src/chkcamera/__main__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) 2019-2020 Latona. All rights reserved. 4 | 5 | from . import main 6 | 7 | if __name__ == "__main__": 8 | main() 9 | -------------------------------------------------------------------------------- /src/chkcamera/core.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) 2019-2020 Latona. All rights reserved. 4 | 5 | """check-multiple-camera-connection 6 | check-multiple-camera-connection has roles which manage whether usb camera is attached or not and 7 | tell this result via kanban or database 8 | """ 9 | 10 | from aion.microservice import main_decorator, Options, WITHOUT_KANBAN 11 | from aion.logger import lprint 12 | import aion.mysql as mysql 13 | from time import sleep 14 | from datetime import datetime, timedelta 15 | import subprocess 16 | 17 | SERVICE_NAME = "check-multiple-camera-connection" 18 | EXECUTE_INTERVAL = 1 19 | METADATA_KEY = "device_list" 20 | 21 | 22 | class DeviceMonitorByGstreamer: 23 | """DeviceMonitortByGstreamer 24 | Search usb deviced by v4l2-ctl command 25 | """ 26 | def __init__(self): 27 | """ 28 | set initial value. these command is used to get device list from host os. 29 | Attributes: 30 | cmd(str): command to get device list 31 | cmd_id(str): command to get serial of each usb device 32 | """ 33 | self.cmd = "v4l2-ctl --list-devices" 34 | self.cmd_id = "ls -l /devices/v4l/by-id" 35 | 36 | def get_device_list(self): 37 | """ 38 | get device list by v4l2-ctl command 39 | Returns: 40 | list: device list 41 | """ 42 | device_list = {} 43 | p = subprocess.Popen(self.cmd.split(), stdout=subprocess.PIPE) 44 | ret = p.communicate()[0].decode() 45 | ret = list(filter(None, ret.split("\n"))) 46 | for itr in range(0, len(ret), 2): 47 | device_list[ret[itr]] = ret[itr + 1].replace("\t", "") 48 | return device_list 49 | 50 | def get_device_list_id(self): 51 | """ 52 | get device list with id. this func is developed to get a serial of a device. 53 | Returns: 54 | dict: {name: device_id} 55 | """ 56 | devices = {} 57 | try: 58 | res = subprocess.check_output(self.cmd_id.split()) 59 | by_id = res.decode() 60 | except Exception as e: 61 | print(e) 62 | return {} 63 | for line in by_id.split('\n'): 64 | if ('../../video' in line): 65 | lst = line.split(' ') 66 | name = ''.join(lst[-3].split('-')[1:-2]) 67 | deviceId = lst[-1] 68 | deviceId = deviceId.replace('../../', '/devices/') 69 | devices[name] = deviceId 70 | return devices 71 | 72 | class UpdateDeviceStateToDB(mysql.BaseMysqlAccess): 73 | """UpdateDeviceStateToDB 74 | Store device list to mysql 75 | This function will be deleted. 76 | """ 77 | def __init__(self): 78 | super().__init__("PeripheralDevice") 79 | 80 | def update_up_device_state(self, serial, path): 81 | """ 82 | update device state in mysql 83 | Args: 84 | serial(str): a serial number of a device 85 | path(str): device path 86 | """ 87 | sql = """ 88 | INSERT INTO cameras(serial, path, state) 89 | VALUES (%(serial)s,%(path)s, %(state)s) 90 | ON DUPLICATE KEY UPDATE 91 | path = IF(path = %(path)s, path, values(path)), 92 | state = IF(state = %(state)s, state, values(state)), 93 | timestamp = CURRENT_TIMESTAMP() 94 | """ 95 | args = {"serial": serial, "path": path, "state": 1} 96 | self.set_query(sql, args) 97 | 98 | def update_down_device_state(self): 99 | """ 100 | update device state in mysql which is not attached 101 | """ 102 | now = datetime.now() - timedelta(seconds=1) 103 | sql = """ 104 | UPDATE cameras 105 | SET path = "", state = 0 106 | WHERE timestamp < %(time)s 107 | """ 108 | args = {"time": now.strftime('%Y-%m-%d %H:%M:%S')} 109 | self.set_query(sql, args) 110 | 111 | def check_invalid_state(self): 112 | """ 113 | update device state in mysql which is not attached 114 | """ 115 | now = datetime.now() + timedelta(seconds=10) 116 | sql = """ 117 | UPDATE cameras 118 | SET path = "", state = 0 119 | WHERE timestamp > %(time)s 120 | """ 121 | args = {"time": now.strftime('%Y-%m-%d %H:%M:%S')} 122 | self.set_query(sql, args) 123 | 124 | 125 | @main_decorator(SERVICE_NAME, WITHOUT_KANBAN) 126 | def main(opt: Options): 127 | conn = opt.get_conn() 128 | num = opt.get_number() 129 | 130 | dm = DeviceMonitorByGstreamer() 131 | metadata = {METADATA_KEY: {}} 132 | while True: 133 | device_list = dm.get_device_list_id() 134 | if metadata[METADATA_KEY] != device_list: 135 | lprint("change device status: ", device_list) 136 | metadata = {METADATA_KEY: device_list} 137 | for serial, device_id in device_list.items(): 138 | # output after kanban 139 | conn.output_kanban( 140 | connection_key=device_id.split('/')[-1], 141 | metadata={METADATA_KEY: {serial: device_id}}, 142 | ) 143 | sleep(EXECUTE_INTERVAL) 144 | try: 145 | with UpdateDeviceStateToDB() as my: 146 | # devices = list(device_list.keys()) 147 | for device_id, path in device_list.items(): 148 | my.update_up_device_state(device_id, path) 149 | my.update_down_device_state() 150 | my.check_invalid_state() 151 | my.commit_query() 152 | lprint("finish to update camera state") 153 | except Exception as e: 154 | lprint(str(e)) 155 | --------------------------------------------------------------------------------