This commit is contained in:
ascendhuawei
2020-09-16 11:50:53 -07:00
commit a61dda4612
97 changed files with 15410 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.5 (fastrcnn)" project-jdk-type="Python SDK" />
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/presenterserver.iml" filepath="$PROJECT_DIR$/.idea/presenterserver.iml" />
</modules>
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/body_pose/ui/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

+155
View File
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="14eee75f-82cc-4cc5-8088-a98a8c71e208" name="Default" comment="" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="config_parser.py" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/body_pose/src/config_parser.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="688">
<caret line="73" column="61" selection-start-line="73" selection-start-column="30" selection-end-line="73" selection-end-column="65" />
<folding>
<element signature="e#1773#1782#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="NodePackageJsonFileManager">
<packageJsonPaths />
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="292" />
<option name="y" value="44" />
<option name="width" value="1400" />
<option name="height" value="908" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="presenterserver" type="b2602c69:ProjectViewProjectNode" />
<item name="presenterserver" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="presenterserver" type="b2602c69:ProjectViewProjectNode" />
<item name="presenterserver" type="462c0819:PsiDirectoryNode" />
<item name="body_pose" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="presenterserver" type="b2602c69:ProjectViewProjectNode" />
<item name="presenterserver" type="462c0819:PsiDirectoryNode" />
<item name="body_pose" type="462c0819:PsiDirectoryNode" />
<item name="config" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="presenterserver" type="b2602c69:ProjectViewProjectNode" />
<item name="presenterserver" type="462c0819:PsiDirectoryNode" />
<item name="body_pose" type="462c0819:PsiDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="14eee75f-82cc-4cc5-8088-a98a8c71e208" name="Default" comment="" />
<created>1581996809387</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1581996809387</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="64" y="-11" width="1857" height="984" extended-state="6" />
<editor active="true" />
<layout>
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Event Log" side_tool="true" />
<window_info anchor="bottom" id="Database Changes" show_stripe_button="false" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Version Control" show_stripe_button="false" />
<window_info anchor="bottom" id="Python Console" />
<window_info anchor="bottom" id="Terminal" />
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.24959569" />
<window_info anchor="bottom" id="Docker" show_stripe_button="false" />
<window_info anchor="right" id="Database" />
<window_info anchor="right" id="SciView" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info id="Favorites" side_tool="true" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/body_pose/src/config_parser.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="688">
<caret line="73" column="61" selection-start-line="73" selection-start-column="30" selection-end-line="73" selection-end-column="65" />
<folding>
<element signature="e#1773#1782#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
</project>
View File
@@ -0,0 +1,10 @@
[baseconf]
# A socket server address to communicate with presenter agent
# Please ensure that the port does not conflict, only support Ipv4
presenter_server_ip=192.168.1.223
presenter_server_port=7006
# A http server address, you can visit the website by "http//web_server_ip:web_server_port".
# Only support Chrome now.
web_server_ip=192.168.1.223
web_server_port=7007
@@ -0,0 +1,27 @@
[loggers]
keys=root,body_pose
[handlers]
keys=rotatingFileHandler
[formatters]
keys=simpleFmt
[logger_root]
level=DEBUG
handlers=rotatingFileHandler
[logger_body_pose]
level=DEBUG
handlers=rotatingFileHandler
qualname=body_pose
propagate=0
[handler_rotatingFileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFmt
args=("body_pose.log", "a", 10*1024*1024, 2)
[formatter_simpleFmt]
format=%(asctime)s-%(levelname)s-%(filename)s:%(lineno)s %(message)s
@@ -0,0 +1,225 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter socket server module"""
import os
import logging
from logging.config import fileConfig
from google.protobuf.message import DecodeError
import common.presenter_message_pb2 as pb2
from common.channel_manager import ChannelManager
from common.presenter_socket_server import PresenterSocketServer
from body_pose.src.config_parser import ConfigParser
class BodyPoseServer(PresenterSocketServer):
'''A server forbody pose'''
def __init__(self, server_address):
'''init func'''
self.channel_manager = ChannelManager(["image", "video"])
super(BodyPoseServer, self).__init__(server_address)
def _clean_connect(self, sock_fileno, epoll, conns, msgs):
"""
close socket, and clean local variables
Args:
sock_fileno: a socket fileno, return value of socket.fileno()
epoll: a set of select.epoll.
conns: all socket connections registered in epoll
msgs: msg read from a socket
"""
logging.info("clean fd:%s, conns:%s", sock_fileno, conns)
self.channel_manager.clean_channel_resource_by_fd(sock_fileno)
epoll.unregister(sock_fileno)
conns[sock_fileno].close()
del conns[sock_fileno]
del msgs[sock_fileno]
def _process_msg(self, conn, msg_name, msg_data):
"""
Total entrance to process protobuf msg
Args:
conn: a socket connection
msg_name: name of a msg.
msg_data: msg body, serialized by protobuf
Returns:
False:somme error occured
True:succeed
"""
# process open channel request
if msg_name == pb2._OPENCHANNELREQUEST.full_name:
ret = self._process_open_channel(conn, msg_data)
# process image request, receive an image data from presenter agent
elif msg_name == pb2._PRESENTIMAGEREQUEST.full_name:
ret = self._process_image_request(conn, msg_data)
# process heartbeat request, it used to keepalive a channel path
elif msg_name == pb2._HEARTBEATMESSAGE.full_name:
ret = self._process_heartbeat(conn)
else:
logging.error("Not recognized msg type %s", msg_name)
ret = False
return ret
def _response_image_request(self, conn, response, err_code):
"""
Assemble protobuf to response image_request
Message structure like this:
--------------------------------------------------------------------
|total message len | int | 4 bytes |
|-------------------------------------------------------------------
|message name len | byte | 1 byte |
|-------------------------------------------------------------------
|message name | string | xx bytes |
|-------------------------------------------------------------------
|message body | protobuf | xx bytes |
--------------------------------------------------------------------
protobuf structure like this:
--------------------------------------------------------------------
|error_code | enum | PresentDataErrorCode |
|-------------------------------------------------------------------
|error_message | string | xx bytes |
|-------------------------------------------------------------------
enum PresentDataErrorCode {
kPresentDataErrorNone = 0;
kPresentDataErrorUnsupportedType = 1;
kPresentDataErrorUnsupportedFormat = 2;
kPresentDataErrorOther = -1;
}
"""
response.error_code = err_code
ret_code = True
if err_code == pb2.kPresentDataErrorUnsupportedFormat:
response.error_message = "Present data not support format."
logging.error("Present data not support format.")
ret_code = False
elif err_code == pb2.kPresentDataErrorNone:
response.error_message = "Present data ok"
ret_code = True
else:
response.error_message = "Present data not known error."
logging.error("Present data not known error.")
ret_code = False
self.send_message(conn, response, pb2._PRESENTIMAGERESPONSE.full_name)
return ret_code
def _process_image_request(self, conn, msg_data):
"""
Deserialization protobuf and process image_request
Args:
conn: a socket connection
msg_data: a protobuf struct, include image request.
Returns:
protobuf structure like this:
------------------------------------
|format | ImageFormat |
|------------------------------------
|width | uint32 |
|------------------------------------
|height | uint32 |
|------------------------------------
|data | bytes |
------------------------------------
enum ImageFormat {
kImageFormatJpeg = 0;
}
"""
request = pb2.PresentImageRequest()
response = pb2.PresentImageResponse()
# Parse msg_data from protobuf
try:
request.ParseFromString(msg_data)
except DecodeError:
logging.error("ParseFromString exception: Error parsing message")
err_code = pb2.kPresentDataErrorOther
return self._response_image_request(conn, response, err_code)
sock_fileno = conn.fileno()
handler = self.channel_manager.get_channel_handler_by_fd(sock_fileno)
if handler is None:
logging.error("get channel handler failed")
err_code = pb2.kPresentDataErrorOther
return self._response_image_request(conn, response, err_code)
# Currently, image format only support jpeg
if request.format != pb2.kImageFormatJpeg:
logging.error("image format %s not support", request.format)
err_code = pb2.kPresentDataErrorUnsupportedFormat
return self._response_image_request(conn, response, err_code)
rectangle_list = []
if request.rectangle_list:
for one_rectangle in request.rectangle_list:
rectangle = []
rectangle.append(one_rectangle.left_top.x)
rectangle.append(one_rectangle.left_top.y)
rectangle.append(one_rectangle.right_bottom.x)
rectangle.append(one_rectangle.right_bottom.y)
rectangle.append(one_rectangle.label_text)
# add the detection result to list
rectangle_list.append(rectangle)
handler.save_image(request.data, request.width, request.height, rectangle_list)
return self._response_image_request(conn, response,
pb2.kPresentDataErrorNone)
def stop_thread(self):
channel_manager = ChannelManager([])
channel_manager.close_all_thread()
self.set_exit_switch()
def run():
'''Entrance function of Body Pose Server '''
# read config file
config = ConfigParser()
# config log
log_file_path = os.path.join(ConfigParser.root_path, "config/logging.conf")
fileConfig(log_file_path)
logging.getLogger('body_pose')
if not config.config_verify():
return None
logging.info("presenter server is starting...")
server_address = (config.presenter_server_ip,
int(config.presenter_server_port))
return BodyPoseServer(server_address)
@@ -0,0 +1,83 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""pose detection config parser module"""
import os
import configparser
import common.parameter_validation as validate
class ConfigParser():
""" parse configuration from the config.conf"""
__instance = None
def __init__(self):
"""init"""
def __new__(cls):
"""ensure class object is a single instance"""
if cls.__instance is None:
cls.__instance = object.__new__(cls)
cls.config_parser()
return cls.__instance
def config_verify(self):
'''Verify configuration Parameters '''
if not validate.validate_ip(ConfigParser.web_server_ip) or \
not validate.validate_ip(ConfigParser.presenter_server_ip) or \
not validate.validate_port(ConfigParser.web_server_port) or \
not validate.validate_port(ConfigParser.presenter_server_port):
return False
return True
@classmethod
def config_parser(cls):
"""parser config from config.conf"""
config_parser = configparser.ConfigParser()
cls.root_path = ConfigParser.get_rootpath()
config_file = os.path.join(cls.root_path, "config/config.conf")
config_parser.read(config_file)
cls.web_server_ip = config_parser.get('baseconf', 'web_server_ip')
cls.presenter_server_ip = \
config_parser.get('baseconf', 'presenter_server_ip')
cls.web_server_port = config_parser.get('baseconf', 'web_server_port')
cls.presenter_server_port = \
config_parser.get('baseconf', 'presenter_server_port')
@staticmethod
def get_rootpath():
"""get presenter server's root directory."""
path = __file__
idx = path.rfind("src")
return path[0:idx]
+474
View File
@@ -0,0 +1,474 @@
# -*- coding: UTF-8 -*-
#
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""
web application for presenter server.
"""
import os
import re
import random
import base64
import threading
import time
import logging
import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.websocket
import body_pose.src.config_parser as config_parser
from common.channel_manager import ChannelManager
class WebApp:
"""
web application
"""
__instance = None
def __init__(self):
"""
init method
"""
self.channel_mgr = ChannelManager(["image", "video"])
self.request_list = set()
self.lock = threading.Lock()
def __new__(cls, *args, **kwargs):
# if instance is None than create one
if cls.__instance is None:
cls.__instance = object.__new__(cls, *args, **kwargs)
return cls.__instance
def add_channel(self, channel_name):
"""
add channel
@param channel_name name of channel
@return: return add status and message (for error status)
"""
ret = {"ret":"error", "msg":""}
# check channel_name validate,
# channel_name can not be None or length = 0
if channel_name is None:
logging.info("Channel name is None , add channel failed")
ret["msg"] = "Channel name can not be empty"
return ret
# strip channel name
channel_name = channel_name.strip()
# check channel_name emtpy or not
if channel_name == "":
logging.info("Channel name is emtpy , add channel failed")
ret["msg"] = "Channel name can not be empty"
return ret
# length of channel name can not over 25
if len(channel_name) > 25:
logging.info("Length of channel name %s > 25 , add channel failed", channel_name)
ret["msg"] = "Length of channel name should less than 25"
return ret
# define pattern support a-z A-Z and /
pattern = re.compile(r"[a-z]|[A-Z]|[0-9]|/")
tmp = pattern.findall(channel_name)
# check reuslt changed or not
if len(tmp) != len(channel_name):
logging.info("%s contain invalidate character, add channel failed", channel_name)
ret["msg"] = "Channel name only support 0-9, a-z, A-Z /"
return ret
# register channel
flag = self.channel_mgr.register_one_channel(channel_name)
# check register result
if self.channel_mgr.err_code_too_many_channel == flag:
logging.info("Only supports up to 10 channels, add channel failed")
ret["msg"] = "Only supports up to 10 channels"
elif self.channel_mgr.err_code_repeat_channel == flag:
logging.info("%s already exist, add channel failed", channel_name)
ret["msg"] = "Channel %s already exist" % channel_name
else:
logging.info("add channel %s succeed", channel_name)
ret["ret"] = "success"
return ret
def del_channel(self, names):
"""
delete channel
@param names: channel name to be deleted, separated by ','
@return: return add status and message (for error status)
"""
# init ret for return
ret = {"ret":"error", "msg":""}
# check length of names
if names.strip() == "":
logging.info("Channel name is empty, delete channel failed")
ret["msg"] = "Channel name should not be empty"
return ret
# split name for multi name
listname = names.split(",")
# unregister name
for item in listname:
item = item.strip()
# if name is emtpy continu
if item == "":
continue
self.channel_mgr.unregister_one_channel(item)
logging.info("delete channel %s succeed", item)
ret["ret"] = "success"
return ret
def list_channels(self):
"""
list all channels information
"""
# list register channels
ret = self.channel_mgr.list_channels()
# id for every channel item , start with 1
idx = 1
# set id for channel
for item in ret:
item['id'] = idx
idx = idx + 1
return ret
def is_channel_exists(self, name):
"""
view channel content via browser.
@param name : channel name
@return return True if exists. otherwise return False.
"""
return self.channel_mgr.is_channel_exist(name)
def add_requst(self, request):
"""
add request
@param requst: request item to be stored
@note: request can not be same with other request.
request is identified by (channel name ,random number)
so this method do not return value.
"""
with self.lock:
self.request_list.add(request)
def has_request(self, request):
"""
whether request exist or not
@param request: request to be checked.
@return: return True if exists, otherwise return False.
"""
with self.lock:
for item in self.request_list:
# check request equal
if item[0] == request[0] and item[1] == request[1]:
return True
return False
def get_media_data(self, channel_name):
"""
get media data by channel name
@param channel_name: channel to be quest data.
@return return dictionary which have for item
type: identify channel type, for image or video.
image: data to be returned.
fps: just for video type
status: can be error, ok, or loading.
"""
# channel exists or not
if self.is_channel_exists(channel_name) is False:
return {'type': 'unkown', 'image':'', 'fps':0, 'status':'error'}
image_data = self.channel_mgr.get_channel_image(channel_name)
# only for image type.
if image_data is not None:
image_data = base64.b64encode(image_data).decode('utf-8')
return {'type': 'image', 'image':image_data, 'fps':0, 'status':'ok'}
fps = 0 # fps for video
image = None # image for video & image
rectangle_list = None
handler = self.channel_mgr.get_channel_handler_by_name(channel_name)
if handler is not None:
media_type = handler.get_media_type()
# if type is image then get image data
if media_type == "image":
image = handler.get_image_data()
# for video
else:
frame_info = handler.get_frame()
image = frame_info[0]
fps = frame_info[1]
rectangle_list = frame_info[4]
status = "loading"
# decode binary to utf-8 when image is not None
if image is not None:
status = "ok"
image = base64.b64encode(image).decode('utf-8')
return {'type': media_type, 'image':image, 'fps':fps, 'status':status, 'rectangle_list':rectangle_list}
else:
return {'type': 'unkown', 'image':None, 'fps':0, 'status':'loading'}
# pylint: disable=abstract-method
class BaseHandler(tornado.web.RequestHandler):
"""
base handler.
"""
# pylint: disable=abstract-method
class HomeHandler(BaseHandler):
"""
handler index request
"""
@tornado.web.asynchronous
def get(self, *args, **kwargs):
"""
handle home or index request only for get
"""
self.render("home.html", listret=G_WEBAPP.list_channels())
# pylint: disable=abstract-method
class AddHandler(BaseHandler):
"""
handler add request
"""
@tornado.web.asynchronous
def post(self, *args, **kwargs):
"""
handle reqeust for add channel
"""
channel_name = self.get_argument('name', '')
self.finish(G_WEBAPP.add_channel(channel_name))
# pylint: disable=abstract-method
class DelHandler(BaseHandler):
"""
handler delete request
"""
@tornado.web.asynchronous
def post(self, *args, **kwargs):
"""
handel requst for delete channel
"""
channel_name = self.get_argument('name', '')
self.finish(G_WEBAPP.del_channel(channel_name))
# pylint: disable=abstract-method
class ViewHandler(BaseHandler):
"""
handler view request
"""
@tornado.web.asynchronous
def get(self, *args, **kwargs):
"""
handler request for view channel
"""
channel_name = self.get_argument('name', '')
if G_WEBAPP.is_channel_exists(channel_name):
req_id = str(random.random())
G_WEBAPP.add_requst((req_id, channel_name))
self.render('view.html', channel_name=channel_name, req=req_id)
else:
raise tornado.web.HTTPError(404)
class WebSocket(tornado.websocket.WebSocketHandler):
"""
web socket for web page socket quest
"""
def open(self, *args, **kwargs):
"""
called when client request by ws or wss
"""
self.req_id = self.get_argument("req", '', True)
self.channel_name = self.get_argument("name", '', True)
# check request valid or not.
if not G_WEBAPP.has_request((self.req_id, self.channel_name)):
self.close()
@staticmethod
def send_message(obj, message, binary=False):
"""
send message to client.
"""
# check socket exist or not
if not obj.ws_connection or not obj.ws_connection.stream.socket:
return False
ret = False
try:
obj.write_message(message, binary)
ret = True
except tornado.websocket.WebSocketClosedError:
ret = False
return ret
def on_close(self):
"""
called when closed web socket
"""
@tornado.web.asynchronous
@tornado.gen.coroutine
def on_message(self, message):
"""
On recv message from client.
"""
if message == "next":
self.run_task()
def run_task(self):
"""
send image to client
"""
# check channel valid
if not G_WEBAPP.is_channel_exists(self.channel_name) or \
not G_WEBAPP.has_request((self.req_id, self.channel_name)):
self.close()
return
result = G_WEBAPP.get_media_data(self.channel_name)
# sleep 100ms if status not ok for frequently query
if result['status'] != 'ok':
time.sleep(0.1)
# if channel not exist close websocket.
if result['status'] == "error":
self.close()
# send message to client
else:
# close websoket when send failed or for image channel.
ret = WebSocket.send_message(self, result)
if not ret or result['type'] == "image":
self.close()
def get_webapp():
"""
start web applicatioin
"""
# get template file and static file path.
templatepath = os.path.join(config_parser.ConfigParser.get_rootpath(), "ui/templates")
staticfilepath = os.path.join(config_parser.ConfigParser.get_rootpath(), "ui/static")
# create application object.
app = tornado.web.Application(handlers=[(r"/", HomeHandler),
(r"/index", HomeHandler),
(r"/add", AddHandler),
(r"/del", DelHandler),
(r"/view", ViewHandler),
(r"/static/(.*)",
tornado.web.StaticFileHandler,
{"path": staticfilepath}),
(r"/websocket", WebSocket)],
template_path=templatepath)
# create server
http_server = tornado.httpserver.HTTPServer(app)
return http_server
def start_webapp():
"""
start webapp
"""
http_server = get_webapp()
config = config_parser.ConfigParser()
http_server.listen(config.web_server_port, address=config.web_server_ip)
print("Please visit http://" + config.web_server_ip + ":" +
str(config.web_server_port) + " for body pose")
tornado.ioloop.IOLoop.instance().start()
def stop_webapp():
"""
stop web app
"""
tornado.ioloop.IOLoop.instance().stop()
global G_WEBAPP
G_WEBAPP = WebApp()
@@ -0,0 +1,39 @@
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote,
dl, dt, dd, ul, ol, li,
pre,
form, fieldset, legend, button, input, textarea,
th, td {
margin: 0;
padding: 0;
}
body,
button, input, select, textarea /* for ie */ {
font: 14px/1.5 tahoma, \5b8b\4f53, sans-serif;
}
h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: normal;}
address, cite, dfn, em, var { font-style: normal; }
code, kbd, pre, samp { font-family: courier new, courier, monospace; }
small { font-size: 12px; }
ul, ol { list-style: none; }
a { text-decoration: none; }
a:hover { text-decoration: underline; }
sup { vertical-align: text-top; }
sub { vertical-align: text-bottom; }
legend { color: #000; } /* for ie6 */
fieldset, img { border: 0; }
button, input, select, textarea { font-size: 100%; }
table { border-collapse: collapse; border-spacing: 0; }
article, aside, details, figcaption, figure, footer,header, hgroup, menu, nav, section,
summary, time, mark, audio, video {
display: block;
margin: 0;
padding: 0;
}
mark { background: #ff0; }
@@ -0,0 +1,77 @@
#dlg-mask {
display: none;
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10000;
background-color: rgba(0,0,0,0.75)/*rgba(140,140,140,0.4)*/;
}
#dlg-mask * {
color: #000;
box-sizing: border-box;
-moz-box-sizing: border-box; /* Firefox */
-webkit-box-sizing: border-box; /* Safari */
}
#dlg-box {
position: absolute;
width: 350px;
z-index: 200001;
padding: 25px;
background-color: rgba(255, 255, 255, 0.88);
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
}
#dlg-box h1 {
width: 100%;
height: 20px;
line-height: 20px;
margin: 0;
font-weight: bold;
font-size: 18px;
}
#dlg-box p, #dlg-box input {
width: 100%;
margin: 20px 0;
font-size: 15px;
}
#dlg-box p {
width: 100%;
margin: 20px 0;
font-size: 15px;
line-height: 1.2;
word-wrap: break-word;
word-break: normal;
white-space: normal;
}
#dlg-box input {
height: 29px;
cursor: text;
}
#dlg-box div {
height: 30px;
}
#dlg-box div span {
display: inline-block;
float: right;
width: 64px;
height: 30px;
font-size: 15px;
color: #187be1;
text-align: center;
line-height: 30px;
cursor: pointer;
}
#dlg-box div span:hover {
background: #eee;
}
.dot {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -0,0 +1,132 @@
body{
background-color: #edf7f9;
font-family: Arial, , Tahoma;
}
.nav{
width: 100%;
height: 80px;
background-color: #155070;
padding-left: 50px;
padding-right: 50px;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
}
.nav_left{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
}
.nav_left img{
width: 60px;
height: 60px;
margin-right: 20px;
}
.nav_left p span{
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
.box_top{
width: 1024px;
margin: 0 auto;
height: 50px;
line-height: 50px;
font-weight: bold;
}
.box_content{
width: 1024px;
margin: 0 auto;
background-color: #ffffff;
}
.content_top{
height: 50px;
padding-top: 10px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.content_top li{
width: 82px;
height: 32px;
float: left;
border: 1px solid #51a8da;
margin-right: 5px;
color: #51a8da;
display: flex;
align-items: center;
justify-content: space-around;
padding-left: 7px;
padding-right: 7px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
cursor: pointer;
}
.content_top li:nth-child(3){
background-color: #51a8da;
color: #ffffff;
}
.content_mid{
border-top:2px solid #51a8da;
padding-top: 15px;
height: 0;
display: none;
}
.mid_list{
width: 100%;
height: 40px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-right: 0;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.mid_list li{
float: left;
height: 100%;
width: 25%;
line-height: 40px;
padding-left: 20px;
padding-right: 20px;
box-sizing: border-box;
-webkit-box-sizing: border-box;
border-right: 1px solid #ccc;
}
.mid_list li:nth-child(odd){
background-color: #edf7f9;
}
.mid_list li:nth-child(odd) span:nth-child(2){
float: right;
}
.mid_add{
width: 82px;
height: 32px;
background-color: #51a8da;
color: #ffffff;
display: flex;
align-items: center;
justify-content: space-around;
cursor: pointer;
}
.content_bot{
border-top:2px solid #51a8da;
}
#mytable th,td{
height: 40px;
text-align: center;
}
@@ -0,0 +1,71 @@
body{
background-color: #edf7f9;
font-family: Arial, , Tahoma;
}
.nav{
width: 100%;
height: 80px;
background-color: #155070;
padding-left: 50px;
padding-right: 50px;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
box-sizing: border-box;
}
.nav_left{
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
justify-content: space-between;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
}
.nav_left img{
width: 60px;
height: 60px;
margin-right: 20px;
}
.nav_left p span{
font-size: 24px;
font-weight: bold;
color: #ffffff;
}
.refresh{
position: absolute;
left: 50%;
top: 50%;
margin: -6px 0 0 -6px ;
}
.video_top{
width: 1024px;
margin: 0 auto;
height: 50px;
line-height: 50px;
font-weight: bold;
}
.video_content{
width: 1024px;
margin: 0 auto;
}
.video_fps{
text-align: right;
padding-right: 100px;
}
.video_inner{
text-align: center;
position: relative;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

@@ -0,0 +1,97 @@
var dialog = (function Dialog() {
var $mask = $("<div id='dlg-mask'></div>");
//title--
//content--object{type[1 for input, 0 for text], text[display in content]}
//btnFlag--1 for OK+Cancel, 0 for OK
function initHtml(title, content, btnFlag, ok, cancel) {
var div = "";
div += "<div id='dlg-box' class='dot'>";
div += "<h1 class='dot' style=\"margin-bottom:5px\">" + title + "</h1>"
div += "<div style=\"width:100%;height:1px;background-color:#ccc;\"></div>";
if (content.type == 0) {
div += "<p>" + content.text + "</p>"
} else if (content.type == 1) {
div += "<input type='text' value='" + content.text + "' placeholder='" + content.placeholder + "' autocomplete='off'/>";
}
if (btnFlag == 0) {
div += "<div><span class='ok'>OK</span></div>";
} else if (btnFlag == 1) {
div += "<div><span class='ok'>OK</span><span class='cancel'>Cancel</span></div>";
}
div += "</div>";
$mask.html(div);
$mask.find("input").val($mask.find("input").val());
if (content.type == 1) {
$mask.find(".ok").on("click", function() {
var retText = $mask.find("input").val();
hide();
if (ok) {
ok(retText);
}
});
$mask.find(".cancel").on("click", function() {
var retText = $mask.find("input").val();
hide();
if (cancel) {
cancel(retText);
}
});
} else {
$mask.find(".ok").on("click", function() {
hide();
if (ok) {
ok();
}
});
$mask.find(".cancel").on("click", function() {
hide();
if (cancel) {
cancel();
}
});
}
}
function calcBoxPos() {
var $box = $mask.find("#dlg-box");
var mask_w = $mask.outerWidth();
var mask_h = $mask.outerHeight();
var box_w = $box.outerWidth();
var box_h = $box.outerHeight();
var pos_left = (mask_w - box_w) / 2 + "px";
var pos_top = (mask_h - box_h) / 2 + "px";
$box.css("left", pos_left).css("top", pos_top);
}
function show() {
$("body").prepend($mask);
$mask.css("display", "block");
if ($mask.find("input") && $mask.find("input")[0]) {
$mask.find("input")[0].focus();
}
calcBoxPos();
$(window).resize(calcBoxPos);
}
function hide() {
$mask.css("display", "none");
$mask.remove();
}
return {
hide: hide,
tip: function(title, text, ok) {
initHtml(title, { type: 0, text: text }, 0, ok, null);
show();
},
input: function(title, text, placeholder, ok, cancel) {
initHtml(title, { type: 1, text: text, placeholder: placeholder }, 1, ok, cancel);
show();
},
confirm: function(title, text, ok, cancel) {
initHtml(title, { type: 0, text: text }, 1, ok, cancel);
show();
}
}
})();
+1
View File
@@ -0,0 +1 @@
var dialog = (function Dialog() { var e = $("<div id='dlg-mask'></div>"); function d(j, h, i, f, g) { var k = ""; k += "<div id='dlg-box' class='dot'>"; k += "<h1 class='dot'>" + j + "</h1>"; if (h.type == 0) { k += "<p>" + h.text + "</p>" } else { if (h.type == 1) { k += "<input type='text' value='" + h.text + "' placeholder='" + h.placeholder + "' autocomplete='off'/>" } } if (i == 0) { k += "<div><span class='ok'>OK</span></div>" } else { if (i == 1) { k += "<div><span class='ok'>OK</span><span class='cancel'>Cancel</span></div>" } } k += "</div>"; e.html(k); e.find("input").val(e.find("input").val()); if (h.type == 1) { e.find(".ok").on("click", function() { var l = e.find("input").val(); b(); if (f) { f(l) } }); e.find(".cancel").on("click", function() { var l = e.find("input").val(); b(); if (g) { g(l) } }) } else { e.find(".ok").on("click", function() { b(); if (f) { f() } }); e.find(".cancel").on("click", function() { b(); if (g) { g() } }) } } function c() { var k = e.find("#dlg-box"); var h = e.outerWidth(); var j = e.outerHeight(); var l = k.outerWidth(); var g = k.outerHeight(); var f = (h - l) / 2 + "px"; var i = (j - g) / 2 + "px"; k.css("left", f).css("top", i) } function a() { $("body").prepend(e); e.css("display", "block"); if (e.find("input") && e.find("input")[0]) { e.find("input")[0].focus() } c(); $(window).resize(c) } function b() { e.css("display", "none"); e.remove() } return { hide: b, tip: function(h, g, f) { d(h, { type: 0, text: g }, 0, f, null); a() }, input: function(j, i, h, f, g) { d(j, { type: 1, text: i, placeholder: h }, 1, f, g); a() }, confirm: function(i, h, f, g) { d(i, { type: 0, text: h }, 1, f, g); a() } } })();
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,183 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Channels</title>
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/list.css">
<link rel="stylesheet" href="/static/css/dialog.css">
</head>
<body>
<div class="box">
<div class="nav">
<div class="nav_left">
<img src="/static/images/logo.png" alt="">
<p><span>Presenter Server</span></p>
</div>
<div class="nav_right">
</div>
</div>
<div class="box_top">
<span> >View List</span>
</div>
<div style="width:100%;height:2px;background-color:#ccc;"></div>
<div class="box_content">
<ul class="content_top">
<li class="top_refresh"><img src="/static/images/u1.png" alt=""><span>Refresh</span></li>
<li class="top_del"><img src="/static/images/u2.png" alt=""><span>Delete</span></li>
</ul>
<div class="content_bot">
<form name="myForm">
<table id="mytable" border="1" width="100%">
<tr>
<th></th>
<th><input type="checkbox" id="checkAll"></th>
<th>Status</th>
<th>View Name</th>
</tr>
{% for item in listret %}
<tr>
<td>{{item['id']}}</td>
<td><input type="checkbox" name="selectFlag" value="1"></td>
{% if item['status'] == 1 %}
<td><img src="/static/images/u5.png" alt=""></td>
{% else %}
<td><img src="/static/images/u6.png" alt=""></td>
{% end %}
<td ><a class="view_channel" style="cursor:pointer">{{item['name']}}</a></td>
</tr>
{% end %}
</table>
</form>
</div>
</div>
</div>
</body>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script src="/static/js/dialog.js"></script>
<script>
$("#checkAll").click(function() {
if (this.checked) {
$("input[name='selectFlag']:checkbox").each(function() {
$(this).attr("checked", true);
})
} else { //反之 取消全选
$("input[name='selectFlag']:checkbox").each(function() {
$(this).attr("checked", false);
})
}
});
$(".view_channel").click(function(){
var url = "/view?name=" + encodeURIComponent($(this).text());
window.open(url);
}
);
</script>
<script>
function checkNameValidate()
{
var name = $("#cname").val().trim();
if (name.length == 0)
{
dialog.tip("Tips","Channel name can not be empty");
return false;
}
if (name.length > 25)
{
dialog.tip("Tips", "Length of channel name should less than 25" ,function(){});
return false;
}
for (var i = 0; i < name.length; i++)
{
var c = name.charAt(i).charCodeAt();
var flag = ((c >= 48 && c <= 57) || (c >= 97 && c<= 122) || (c>= 65 && c <= 90) || (c == 47));
if (false == flag)
{
dialog.tip("Tips", "Channel name only support 0-9, a-z, A-Z /" ,function(){});
return false;
}
}
return true;
}
$(".top_create").click(function () {
if($(".content_mid").css("display")=="none"){
$(".content_mid").css("display","block").animate({height:"100px"});
}else{
$(".content_mid").animate({height:"0"}).css("display","none");
}
});
$(".mid_add").click(function () {
var rowlen = $("#mytable").find("tr").length;
if (rowlen >= 11)
{
dialog.tip("Tips", "Presenter supports up to 10 channels" ,function(){});
return;
}
//check
if (true == checkNameValidate())
{
var url = "/add?"+"name=" + encodeURIComponent($("#cname").val().trim()) +"&time="+(new Date().getTime());
$.ajax({
type: "POST",
url: url,
dataType: "json",
success: function(data)
{
if (data["ret"] == "success")
{
window.location.reload()
}
else
{
dialog.tip("Tips", data["msg"], function(){});
}
},
});
}
});
$(".top_refresh").click(function () {
window.location.reload();
});
$(".top_del").click(function () {
if($("input[name='selectFlag']").is(":checked")){
var msg = "";
$("input[name='selectFlag']:checked").each(function() {
// 遍历选中的checkbox
tr = $(this).parents("tr");
td = $(tr).find("td");
msg += encodeURIComponent($(td).eq(3).text());
msg += ",";
});
dialog.confirm("Tips", "Are you sure to delete ", function(){
var url = "/del?"+"time="+(new Date().getTime())+"&name="+msg;
$.ajax({
type: "POST",
url: url,
dataType: "json",
success: function(data)
{
if (data["ret"] == "success")
{
window.location.reload()
}
else
{
dialog.tip("Tips", data["msg"], function(){});
}
},
});
}, function(){});
}else{
dialog.tip("Tips", "Please select one item at least", function(){});
}
});
</script>
</html>
@@ -0,0 +1,155 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>view</title>
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/testvideo.css">
</head>
<body>
<div class="video_box">
<div class="nav">
<div class="nav_left">
<img src="/static/images/logo.png" alt="">
<p><span>Presenter Server</span></p>
</div>
<div class="nav_right">
</div>
</div>
<div class="video_top">
<span> >view</span>
</div>
<div style="width:100%;height:2px;background-color:#ccc;"></div>
<div class="video_content">
<div class="video_fps" id='fpswapper' hidden><p><span> channel name: {{ channel_name }} </span> <span>&nbsp;&nbsp;&nbsp;&nbsp;fps:</span><span id='fpsval'></span></p></div>
<div class="video_inner">
<img src="/static/images/loading.gif" id = "loading" board = "1" alt=""/>
<!-- <img id = "load_media" hidden width = "1024px" board = "1" alt=""/> -->
<canvas id="canvas"></canvas>
</div>
</div>
</div>
</body>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script>
var canvas=document.getElementById("canvas")
var ctx=canvas.getContext("2d")
$('#fpswapper').hide()
$('#loading').hide()
function startViewVideo(){
$('#loading').show()
$('#canvas').hide()
var image = new Image();
var wsProtocol = "ws://";
if (window.location.protocol == "https:"){
wsProtocol = "wss://";
}
var wsUrl = wsProtocol + window.location.host+"/websocket?req={{req}}&name={{channel_name}}";
var ws = new WebSocket(wsUrl);
var onmessageflag = false;
ws.onopen = function() {
ws.send('next');
};
var count = 0;
var timestart = 0;
ws.onmessage = function (evt) {
$('#loading').hide()
$('#canvas').show()
var data = JSON.parse(evt.data)
var rectangles = []
if ('ok' == data['status']){
$('#fpsval').text(data.fps);
$('#loading').hide();
// $('#load_media').show();
var src = "data:image/jpeg;base64," + data['image'];
// $('#load_media').attr('src', src);
if (data['type'] == 'video'){
$('#fpswapper').show();
rectangles = data['rectangle_list']
}
var wantedWidth = 1024
var img = new Image()
img.src = src
img.onload=function(){
scale_factor = wantedWidth/img.width
canvas.setAttribute("width",1024)
canvas.setAttribute("height",img.height*scale_factor)
ctx.strokeStyle="yellow"
ctx.font="30px serif"
ctx.fillStyle="yellow"
ctx.strokeStyle="yellow"
ctx.font="30px serif"
ctx.fillStyle="yellow"
ctx.drawImage(img,0,0, wantedWidth, img.height*scale_factor)
for (var index in rectangles){
var pos= rectangles[index].slice(0,4) //
for (var i in pos){
pos[i] = pos[i]*scale_factor
}
var msg = rectangles[index].slice(4,5)
//add space between msg and face
//if upper space is not enough show the msg at the bottom
if(50>pos[1]){
// ctx.fillText(msg,pos[0],pos[3]+50)
}
else{
// ctx.fillText(msg,pos[0],pos[1]-10)
}
ctx.beginPath()
// 1/3 space draw line
if(msg == 'points'){
ctx.strokeStyle = "#0F4F00"
ctx.arc(pos[0],pos[1],6,0,2*Math.PI)
ctx.fillStyle = "#6CFF33"
ctx.fill()
}else if(msg == 'hand_box'){
ctx.strokeStyle = "#FF0000"
ctx.lineWidth = 5
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[2],pos[1])
ctx.moveTo(pos[2],pos[1])
ctx.lineTo(pos[2],pos[3])
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[0],pos[3])
ctx.moveTo(pos[0],pos[3])
ctx.lineTo(pos[2],pos[3])
// }else if(msg == 'hand_point'){
// ctx.strokeStyle = "#33FAFF"
// ctx.arc(pos[0],pos[1],3,0,2*Math.PI)
// ctx.fillStyle = "#33FAFF"
// ctx.fill()
// }else if(msg == 'hand'){
// ctx.strokeStyle = "#FC33FF"
// ctx.lineWidth = 3
// ctx.moveTo(pos[0],pos[1])
// ctx.lineTo(pos[2],pos[3])
}else if(msg == 'body'){
ctx.strokeStyle = "#FFF700"
ctx.lineWidth = 5
ctx.moveTo(pos[0],pos[1])
ctx.lineTo(pos[2],pos[3])
}else{
ctx.font = "30px Arial";
ctx.lineWidth = 5
ctx.fillStyle = "#33FAFF"
ctx.fillText(msg, 200, 200);
}
ctx.stroke()
}
}
}
ws.send('next');
}
}
startViewVideo();
</script>
</html>
View File
+209
View File
@@ -0,0 +1,209 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter app manager module"""
import time
import threading
import logging
from common.channel_manager import ChannelManager
# Heartbeat timeout, exceeding the limit, the socket will disconnect
HEARTBEAT_TIMEOUT = 100
class App():
'''App class, When receive an app request from
Presenter Agent, creat an object.
'''
def __init__(self, app_id, conn=None):
self.app_id = app_id
self.heartbeat = time.time()
self.socket_fd = conn.fileno()
# set timeout 1 second
conn.settimeout(1)
self.socket = conn
self.frame_num_dict = {}
class AppManager():
'''A class provides app management features'''
__instance = None
channel_manager = None
app_list_lock = threading.Lock()
app_list = []
thread_switch = False
def __init__(self):
"""init func"""
def __new__(cls):
"""ensure only a single instance created. """
if cls.__instance is None:
cls.__instance = object.__new__(cls)
cls.channel_manager = ChannelManager([])
cls._create_thread()
return cls.__instance
@classmethod
def _create_thread(cls):
"""_create_thread."""
thread = threading.Thread(target=cls._app_thread)
thread.start()
@classmethod
def _app_thread(cls):
"""background thread to process video"""
logging.info('create app manager thread')
while True:
if cls.thread_switch:
break
for i in range(len(cls.app_list)):
if time.time() - cls.app_list[i].heartbeat > HEARTBEAT_TIMEOUT:
app_id = cls.app_list[i].app_id
cls.channel_manager.unregister_one_channel(app_id)
del cls.app_list[i]
logging.info("unregister app: %s", app_id)
time.sleep(1)
def set_thread_switch(self):
AppManager.thread_switch = True
def register_app(self, app_id, socket):
"""
API for registering an app
Args:
app_id: app id, must be globally unique
socket: a socket communicating with the app
"""
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].app_id == app_id:
return False
app = App(app_id, socket)
self.app_list.append(app)
self.channel_manager.register_one_channel(app_id)
logging.info("register app: %s", app_id)
return True
def unregister_app_by_fd(self, sock_fileno):
"""
API for unregistering an app
Args:
sock_fileno: sock_fileno is binded to an app.
Through it, find the app and delete it.
"""
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].socket_fd == sock_fileno:
app_id = self.app_list[i].app_id
self.channel_manager.unregister_one_channel(app_id)
del self.app_list[i]
logging.info("unregister app: %s", app_id)
break
def get_socket_by_app_id(self, app_id):
"""
API for finding an app
Args:
app_id: the id of an app.
"""
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].app_id == app_id:
return self.app_list[i].socket
return None
def get_app_id_by_socket(self, sock_fd):
"""
API for get app id by socket
Args:
sock_fd: sock_fd is binded to an app.
Through it, find the app and delete it.
"""
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].socket_fd == sock_fd:
return self.app_list[i].app_id
return None
def is_app_exist(self, app_id):
"""
API for checking if the app exist
Args:
app_id: the id of an app.
"""
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].app_id == app_id:
return True
return False
def get_app_num(self):
"""
API for getting the number of apps
Args: NA
"""
with self.app_list_lock:
return len(self.app_list)
def set_heartbeat(self, sock_fileno):
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].socket_fd == sock_fileno:
self.app_list[i].heartbeat = time.time()
def increase_frame_num(self, app_id, channel_id):
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].app_id == app_id:
if channel_id in self.app_list[i].frame_num_dict:
self.app_list[i].frame_num_dict[channel_id] += 1
else:
self.app_list[i].frame_num_dict[channel_id] = 1
def get_frame_num(self, app_id, channel_id):
with self.app_list_lock:
for i in range(len(self.app_list)):
if self.app_list[i].app_id == app_id:
if channel_id in self.app_list[i].frame_num_dict:
return self.app_list[i].frame_num_dict[channel_id]
else:
return 0
return 0
def list_app(self):
"""
API for listing all apps
Args: NA
"""
with self.app_list_lock:
return [self.app_list[i].app_id for i in range(len(self.app_list))]
+227
View File
@@ -0,0 +1,227 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter channel manager module"""
import time
import logging
import threading
from threading import get_ident
from common.channel_manager import ChannelManager
# thread event timeout, The unit is second.
WEB_EVENT_TIMEOUT = 2
# thread event timeout, The unit is second.
IMAGE_EVENT_TIMEOUT = 10
# heart beat timeout, The unit is second.
HEARTBEAT_TIMEOUT = 100
class ThreadEvent():
"""An Event-like class that signals all active clients when a new frame is
available.
"""
def __init__(self, timeout=None):
self.events = {}
self.timeout = timeout
def wait(self):
"""Invoked from each client's thread to wait for the next frame."""
ident = get_ident()
if ident not in self.events:
# this is a new client
# add an entry for it in the self.events dict
# each entry has two elements, a threading.Event() and a timestamp
self.events[ident] = [threading.Event(), time.time()]
return self.events[ident][0].wait(self.timeout)
def set(self):
"""Invoked by the camera thread when a new frame is available."""
now = time.time()
remove = None
for ident, event in self.events.items():
if not event[0].isSet():
# if this client's event is not set, then set it
# also update the last set timestamp to now
event[0].set()
event[1] = now
else:
# if the client's event is already set, it means the client
# did not process a previous frame
# if the event stays set for more than 5 seconds, then assume
# the client is gone and remove it
if now - event[1] > 5:
remove = ident
if remove:
del self.events[remove]
def clear(self):
"""Invoked from each client's thread after a frame was processed."""
self.events[get_ident()][0].clear()
class ChannelHandler():
"""A set of channel handlers, process data received from channel"""
def __init__(self, channel_name, media_type):
self.channel_name = channel_name
self.media_type = media_type
self.img_data = None
self._frame = None
self.thread = None
self._frame = None
# last time the channel receive data.
self.heartbeat = time.time()
self.web_event = ThreadEvent(timeout=WEB_EVENT_TIMEOUT)
self.image_event = ThreadEvent(timeout=IMAGE_EVENT_TIMEOUT)
self.lock = threading.Lock()
self.channel_manager = ChannelManager([])
self.rectangle_list = None
if media_type == "video":
self.thread_name = "videothread-{}".format(self.channel_name)
self.heartbeat = time.time()
self.close_thread_switch = False
self.fps = 0
self.image_number = 0
self.time_list = []
self._create_thread()
def close_thread(self):
"""close thread if object has created"""
if self.thread is None:
return
self.set_thread_switch()
self.image_event.set()
logging.info("%s set _close_thread_switch True", self.thread_name)
def set_heartbeat(self):
"""record heartbeat"""
self.heartbeat = time.time()
def set_thread_switch(self):
"""record heartbeat"""
self.close_thread_switch = True
def save_image(self, data, width, height, rectangle_list):
"""save image receive from socket"""
self.width = width
self.height = height
self.rectangle_list = rectangle_list
# compute fps if type is video
if self.media_type == "video":
while self.img_data:
time.sleep(0.01)
self.time_list.append(self.heartbeat)
self.image_number += 1
while self.time_list[0] + 1 < time.time():
self.time_list.pop(0)
self.image_number -= 1
if self.image_number == 0:
break
self.fps = len(self.time_list)
self.img_data = data
self.image_event.set()
else:
self.img_data = data
self.channel_manager.save_channel_image(self.channel_name,
self.img_data, self.rectangle_list)
self.heartbeat = time.time()
def get_media_type(self):
"""get media_type, support image or video"""
return self.media_type
def get_image(self):
"""get image_data"""
return self.img_data
def _create_thread(self):
"""Start the background video thread if it isn't running yet."""
if self.thread is not None and self.thread.isAlive():
return
# start background frame thread
self.thread = threading.Thread(target=self._video_thread)
self.thread.start()
def get_frame(self):
"""Return the current video frame."""
# wait util receive a frame data, and push it to your browser.
ret = self.web_event.wait()
self.web_event.clear()
# True: _web_event return because set()
# False: _web_event return because timeout
if ret:
return (self._frame, self.fps, self.width, self.height, self.rectangle_list)
return (None, None, None, None, None)
def frames(self):
"""a generator generates image"""
while True:
self.image_event.wait()
self.image_event.clear()
if self.img_data:
yield self.img_data
self.img_data = None
# if set _close_thread_switch, return immediately
if self.close_thread_switch:
yield None
# if no frames or heartbeat coming in the last 100 seconds,
# stop the thread and close socket
if time.time() - self.heartbeat > HEARTBEAT_TIMEOUT:
self.set_thread_switch()
self.img_data = None
yield None
def _video_thread(self):
"""background thread to process video"""
logging.info('create %s...', (self.thread_name))
for frame in self.frames():
if frame:
# send signal to clients
self._frame = frame
self.web_event.set()
# exit thread
if self.close_thread_switch:
self.channel_manager.clean_channel_resource_by_name(
self.channel_name)
logging.info('Stop thread:%s.', (self.thread_name))
break
+291
View File
@@ -0,0 +1,291 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter channel manager module"""
import logging
import threading
# max support 10 channels
MAX_CHANNEL_NUM = 10
# when a channel have receive data,
# the active status will last 3 seconds
ACTIVE_LAST_TIME = 3
class ChannelResource():
"""every channel has a ChannelResource object, contains a ChannelHandler object
and a socket fileno. it corresponding to the ChannelFd one by one
"""
def __init__(self, handler, socket=None):
self.handler = handler
self.socket = socket
class ChannelFd():
"""every channel has a ChannelFd object, contains a ChannelHandler
object and channel name. It corresponds to the ChannelResource one by one
"""
def __init__(self, channel_name, handler):
self.channel_name = channel_name
self.handler = handler
class Channel():
"""record user register channels
self.image: if channel type is image, save the image here
"""
def __init__(self, channel_name):
self.channel_name = channel_name
self.image = None
self.rectangle_list = None
class ChannelManager():
"""manage all the api about channel
__instance: ensure it is a single instance
_channel_resources: a dict
key: channel name
value: a ChannelResource() object.
_channel_fds: a dict
key: socket fileno
value: a ChannelFd() object.
_channel_list: a list, member is a Channel() object."""
__instance = None
channel_resources = {}
channel_fds = {}
channel_list = []
channel_resource_lock = threading.Lock()
channel_fds_lock = threading.Lock()
channel_lock = threading.Lock()
err_code_ok = 0
err_code_too_many_channel = 1
err_code_repeat_channel = 2
def __init__(self, channel_list=None):
"""init func"""
def __new__(cls, channel_list=None):
"""ensure only a single instance created. """
if cls.__instance is None:
cls.__instance = object.__new__(cls)
# default create 2 channels: image and video
# if channel_list is not None and isinstance(channel_list, list):
# for i in channel_list:
# cls.channel_list.append(Channel(channel_name=i))
# logging.info("register channel %s", i)
return cls.__instance
def _register_channel_fd(self, sock_fileno, channel_name):
"""Internal func, create a ChannelFd object"""
if self.channel_fds.get(sock_fileno):
del self.channel_fds[sock_fileno]
handler = self.channel_resources[channel_name].handler
self.channel_fds[sock_fileno] = ChannelFd(channel_name, handler)
def create_channel_resource(self, channel_name,
channel_fd,
media_type,
handler):
"""create a ChannelResource object which contains all the resources
binding a channel.
channel_name: channel name.
channel_fd: socket fileno binding the channel.
media_type: support image or video.
handler: an channel handler process image data.
"""
with self.channel_resource_lock:
log_info = "create channel resource,"
log_info += " channel_name:%s, channel_fd:%u, media_type:%s"
logging.info(log_info, channel_name, channel_fd, media_type)
self.channel_resources[channel_name] = \
ChannelResource(handler=handler, socket=channel_fd)
self._register_channel_fd(channel_fd, channel_name)
def _clean_channel_resource(self, channel_name):
"""Internal func, clean channel resource by channel name"""
if self.channel_resources.get(channel_name):
self.channel_resources[channel_name].handler.close_thread()
self.channel_resources[channel_name].handler.web_event.set()
self.channel_resources[channel_name].handler.image_event.set()
del self.channel_resources[channel_name]
logging.info("clean channel: %s's resource", channel_name)
def clean_channel_resource_by_fd(self, sock_fileno):
"""
clean channel resource by socket fileno
sock_fileno: socket fileno which binding to an channel
"""
with self.channel_fds_lock:
with self.channel_resource_lock:
if self.channel_fds.get(sock_fileno):
self._clean_channel_resource(
self.channel_fds[sock_fileno].channel_name)
del self.channel_fds[sock_fileno]
def clean_channel_resource_by_name(self, channel_name):
"""clean channel resource by channel_name
channel_name: channel name"""
if self.channel_resources.get(channel_name):
self.clean_channel_resource_by_fd(
self.channel_resources[channel_name].socket)
def get_channel_handler_by_fd(self, sock_fileno):
"""get channel handler by socket fileno"""
with self.channel_fds_lock:
if self.channel_fds.get(sock_fileno):
return self.channel_fds[sock_fileno].handler
return None
def is_channel_busy(self, channel_name):
"""check if channel is busy """
with self.channel_resource_lock:
if self.channel_resources.get(channel_name):
return True
return False
def close_all_thread(self):
"""if a channel process video type, it will create a thread.
this func can close the thread.
"""
with self.channel_resource_lock:
for channel_name in self.channel_resources:
self.channel_resources[channel_name].handler.close_thread()
def get_channel_handler_by_name(self, channel_name):
"""
get the channel handlerby channel name
"""
with self.channel_resource_lock:
if self.channel_resources.get(channel_name):
return self.channel_resources[channel_name].handler
return None
def list_channels(self):
"""
return all the channel name and the status
status is indicating active state or not
"""
with self.channel_lock:
return [{'status': self.is_channel_busy(i.channel_name),
'name': i.channel_name} for i in self.channel_list]
def register_one_channel(self, channel_name):
"""
register a channel path, user create a channel via browser
"""
with self.channel_lock:
if len(self.channel_list) >= MAX_CHANNEL_NUM:
logging.info("register channel: %s fail, \
exceed max number 10.", channel_name)
return self.err_code_too_many_channel
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
logging.info("register channel: %s fail, \
already exist.", channel_name)
return self.err_code_repeat_channel
self.channel_list.append(Channel(channel_name=channel_name))
logging.info("register channel: %s", channel_name)
return self.err_code_ok
def unregister_one_channel(self, channel_name):
"""
unregister a channel path, user delete a channel via browser
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
self.clean_channel_resource_by_name(channel_name)
logging.info("unregister channel: %s", channel_name)
del self.channel_list[i]
break
def is_channel_exist(self, channel_name):
"""
Check if a channel is exist
True: exist
False: not exist
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
return True
return False
def save_channel_image(self, channel_name, image_data, rectangle_list):
"""
when a channel bounding to image type,
server will permanent hold an image for it.
this func save a image in memory
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
self.channel_list[i].image = image_data
self.channel_list[i].rectangle_list = rectangle_list
break
def get_channel_image(self, channel_name):
"""
when a channel bounding to image type,
server will permanent hold an image for it.
this func get the image
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
return self.channel_list[i].image
# channel not exist
return None
def get_channel_image_with_rectangle(self, channel_name):
"""
A new method for display server,
return the image and rectangle list
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
return (self.channel_list[i].image, self.channel_list[i].rectangle_list)
return (None, None)
def clean_channel_image(self, channel_name):
"""
when a channel bounding to image type,
server will permanent hold an image for it.
this func clean the image
"""
with self.channel_lock:
for i in range(len(self.channel_list)):
if self.channel_list[i].channel_name == channel_name:
self.channel_list[i].image = None
break
@@ -0,0 +1,98 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""Parameter Validation module"""
import logging
PORT_INTERVAL_BEGIN = 1024
PORT_INTERVAL_END = 49151
def validate_ip(ip_str):
if ip_str == '0.0.0.0':
logging.error("IP Addr \"0.0.0.0\" is illegal")
print("IP Addr \"0.0.0.0\" is illegal")
return False
sep = ip_str.split('.')
if len(sep) != 4:
return False
for i, x in enumerate(sep):
try:
int_x = int(x)
if int_x < 0 or int_x > 255:
logging.error("Illegal ip: %s", ip_str)
print("Illegal ip: %s"%ip_str)
return False
except ValueError:
logging.error("IP format error:%s", ip_str)
print("IP format error:%s"%ip_str)
return False
return True
def validate_port(value_str):
try:
value = int(value_str)
if value < PORT_INTERVAL_BEGIN or value > PORT_INTERVAL_END:
logging.error("Illegal port: %d", value)
print("Illegal port: %d"%value)
return False
except ValueError:
logging.error("Port format error:%s", value_str)
print("Port format error:%s"%value_str)
return False
return True
def validate_integer(value_str, begin, end):
try:
value = int(value_str)
if value < begin or value > end:
return False
except ValueError:
return False
return True
def Integer_greater(value_str, compared_value):
try:
value = int(value_str)
if value < compared_value:
return False
except ValueError:
return False
return True
def validate_float(value_str, begin, end):
try:
value = float(value_str)
if value < begin or value > end:
return False
except ValueError:
return False
return True
@@ -0,0 +1,525 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: presenter_message.proto
import sys
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor.FileDescriptor(
name='presenter_message.proto',
package='ascend.presenter.proto',
syntax='proto3',
serialized_pb=_b('\n\x17presenter_message.proto\x12\x16\x61scend.presenter.proto\"l\n\x12OpenChannelRequest\x12\x14\n\x0c\x63hannel_name\x18\x01 \x01(\t\x12@\n\x0c\x63ontent_type\x18\x02 \x01(\x0e\x32*.ascend.presenter.proto.ChannelContentType\"n\n\x13OpenChannelResponse\x12@\n\nerror_code\x18\x01 \x01(\x0e\x32,.ascend.presenter.proto.OpenChannelErrorCode\x12\x15\n\rerror_message\x18\x02 \x01(\t\"\x12\n\x10HeartbeatMessage\"\"\n\nCoordinate\x12\t\n\x01x\x18\x01 \x01(\r\x12\t\n\x01y\x18\x02 \x01(\r\"\x94\x01\n\x0eRectangle_Attr\x12\x34\n\x08left_top\x18\x01 \x01(\x0b\x32\".ascend.presenter.proto.Coordinate\x12\x38\n\x0cright_bottom\x18\x02 \x01(\x0b\x32\".ascend.presenter.proto.Coordinate\x12\x12\n\nlabel_text\x18\x03 \x01(\t\"\xb7\x01\n\x13PresentImageRequest\x12\x33\n\x06\x66ormat\x18\x01 \x01(\x0e\x32#.ascend.presenter.proto.ImageFormat\x12\r\n\x05width\x18\x02 \x01(\r\x12\x0e\n\x06height\x18\x03 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12>\n\x0erectangle_list\x18\x05 \x03(\x0b\x32&.ascend.presenter.proto.Rectangle_Attr\"o\n\x14PresentImageResponse\x12@\n\nerror_code\x18\x01 \x01(\x0e\x32,.ascend.presenter.proto.PresentDataErrorCode\x12\x15\n\rerror_message\x18\x02 \x01(\t*\xa5\x01\n\x14OpenChannelErrorCode\x12\x19\n\x15kOpenChannelErrorNone\x10\x00\x12\"\n\x1ekOpenChannelErrorNoSuchChannel\x10\x01\x12)\n%kOpenChannelErrorChannelAlreadyOpened\x10\x02\x12#\n\x16kOpenChannelErrorOther\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01*P\n\x12\x43hannelContentType\x12\x1c\n\x18kChannelContentTypeImage\x10\x00\x12\x1c\n\x18kChannelContentTypeVideo\x10\x01*#\n\x0bImageFormat\x12\x14\n\x10kImageFormatJpeg\x10\x00*\xa4\x01\n\x14PresentDataErrorCode\x12\x19\n\x15kPresentDataErrorNone\x10\x00\x12$\n kPresentDataErrorUnsupportedType\x10\x01\x12&\n\"kPresentDataErrorUnsupportedFormat\x10\x02\x12#\n\x16kPresentDataErrorOther\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x62\x06proto3')
)
_OPENCHANNELERRORCODE = _descriptor.EnumDescriptor(
name='OpenChannelErrorCode',
full_name='ascend.presenter.proto.OpenChannelErrorCode',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='kOpenChannelErrorNone', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kOpenChannelErrorNoSuchChannel', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kOpenChannelErrorChannelAlreadyOpened', index=2, number=2,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kOpenChannelErrorOther', index=3, number=-1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=780,
serialized_end=945,
)
_sym_db.RegisterEnumDescriptor(_OPENCHANNELERRORCODE)
OpenChannelErrorCode = enum_type_wrapper.EnumTypeWrapper(_OPENCHANNELERRORCODE)
_CHANNELCONTENTTYPE = _descriptor.EnumDescriptor(
name='ChannelContentType',
full_name='ascend.presenter.proto.ChannelContentType',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='kChannelContentTypeImage', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kChannelContentTypeVideo', index=1, number=1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=947,
serialized_end=1027,
)
_sym_db.RegisterEnumDescriptor(_CHANNELCONTENTTYPE)
ChannelContentType = enum_type_wrapper.EnumTypeWrapper(_CHANNELCONTENTTYPE)
_IMAGEFORMAT = _descriptor.EnumDescriptor(
name='ImageFormat',
full_name='ascend.presenter.proto.ImageFormat',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='kImageFormatJpeg', index=0, number=0,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=1029,
serialized_end=1064,
)
_sym_db.RegisterEnumDescriptor(_IMAGEFORMAT)
ImageFormat = enum_type_wrapper.EnumTypeWrapper(_IMAGEFORMAT)
_PRESENTDATAERRORCODE = _descriptor.EnumDescriptor(
name='PresentDataErrorCode',
full_name='ascend.presenter.proto.PresentDataErrorCode',
filename=None,
file=DESCRIPTOR,
values=[
_descriptor.EnumValueDescriptor(
name='kPresentDataErrorNone', index=0, number=0,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kPresentDataErrorUnsupportedType', index=1, number=1,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kPresentDataErrorUnsupportedFormat', index=2, number=2,
options=None,
type=None),
_descriptor.EnumValueDescriptor(
name='kPresentDataErrorOther', index=3, number=-1,
options=None,
type=None),
],
containing_type=None,
options=None,
serialized_start=1067,
serialized_end=1231,
)
_sym_db.RegisterEnumDescriptor(_PRESENTDATAERRORCODE)
PresentDataErrorCode = enum_type_wrapper.EnumTypeWrapper(_PRESENTDATAERRORCODE)
kOpenChannelErrorNone = 0
kOpenChannelErrorNoSuchChannel = 1
kOpenChannelErrorChannelAlreadyOpened = 2
kOpenChannelErrorOther = -1
kChannelContentTypeImage = 0
kChannelContentTypeVideo = 1
kImageFormatJpeg = 0
kPresentDataErrorNone = 0
kPresentDataErrorUnsupportedType = 1
kPresentDataErrorUnsupportedFormat = 2
kPresentDataErrorOther = -1
_OPENCHANNELREQUEST = _descriptor.Descriptor(
name='OpenChannelRequest',
full_name='ascend.presenter.proto.OpenChannelRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='channel_name', full_name='ascend.presenter.proto.OpenChannelRequest.channel_name', index=0,
number=1, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='content_type', full_name='ascend.presenter.proto.OpenChannelRequest.content_type', index=1,
number=2, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=51,
serialized_end=159,
)
_OPENCHANNELRESPONSE = _descriptor.Descriptor(
name='OpenChannelResponse',
full_name='ascend.presenter.proto.OpenChannelResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='error_code', full_name='ascend.presenter.proto.OpenChannelResponse.error_code', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='error_message', full_name='ascend.presenter.proto.OpenChannelResponse.error_message', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=161,
serialized_end=271,
)
_HEARTBEATMESSAGE = _descriptor.Descriptor(
name='HeartbeatMessage',
full_name='ascend.presenter.proto.HeartbeatMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=273,
serialized_end=291,
)
_COORDINATE = _descriptor.Descriptor(
name='Coordinate',
full_name='ascend.presenter.proto.Coordinate',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='x', full_name='ascend.presenter.proto.Coordinate.x', index=0,
number=1, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='y', full_name='ascend.presenter.proto.Coordinate.y', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=293,
serialized_end=327,
)
_RECTANGLE_ATTR = _descriptor.Descriptor(
name='Rectangle_Attr',
full_name='ascend.presenter.proto.Rectangle_Attr',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='left_top', full_name='ascend.presenter.proto.Rectangle_Attr.left_top', index=0,
number=1, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='right_bottom', full_name='ascend.presenter.proto.Rectangle_Attr.right_bottom', index=1,
number=2, type=11, cpp_type=10, label=1,
has_default_value=False, default_value=None,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='label_text', full_name='ascend.presenter.proto.Rectangle_Attr.label_text', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=330,
serialized_end=478,
)
_PRESENTIMAGEREQUEST = _descriptor.Descriptor(
name='PresentImageRequest',
full_name='ascend.presenter.proto.PresentImageRequest',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='format', full_name='ascend.presenter.proto.PresentImageRequest.format', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='width', full_name='ascend.presenter.proto.PresentImageRequest.width', index=1,
number=2, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='height', full_name='ascend.presenter.proto.PresentImageRequest.height', index=2,
number=3, type=13, cpp_type=3, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='data', full_name='ascend.presenter.proto.PresentImageRequest.data', index=3,
number=4, type=12, cpp_type=9, label=1,
has_default_value=False, default_value=_b(""),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='rectangle_list', full_name='ascend.presenter.proto.PresentImageRequest.rectangle_list', index=4,
number=5, type=11, cpp_type=10, label=3,
has_default_value=False, default_value=[],
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=481,
serialized_end=664,
)
_PRESENTIMAGERESPONSE = _descriptor.Descriptor(
name='PresentImageResponse',
full_name='ascend.presenter.proto.PresentImageResponse',
filename=None,
file=DESCRIPTOR,
containing_type=None,
fields=[
_descriptor.FieldDescriptor(
name='error_code', full_name='ascend.presenter.proto.PresentImageResponse.error_code', index=0,
number=1, type=14, cpp_type=8, label=1,
has_default_value=False, default_value=0,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='error_message', full_name='ascend.presenter.proto.PresentImageResponse.error_message', index=1,
number=2, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
nested_types=[],
enum_types=[
],
options=None,
is_extendable=False,
syntax='proto3',
extension_ranges=[],
oneofs=[
],
serialized_start=666,
serialized_end=777,
)
_OPENCHANNELREQUEST.fields_by_name['content_type'].enum_type = _CHANNELCONTENTTYPE
_OPENCHANNELRESPONSE.fields_by_name['error_code'].enum_type = _OPENCHANNELERRORCODE
_RECTANGLE_ATTR.fields_by_name['left_top'].message_type = _COORDINATE
_RECTANGLE_ATTR.fields_by_name['right_bottom'].message_type = _COORDINATE
_PRESENTIMAGEREQUEST.fields_by_name['format'].enum_type = _IMAGEFORMAT
_PRESENTIMAGEREQUEST.fields_by_name['rectangle_list'].message_type = _RECTANGLE_ATTR
_PRESENTIMAGERESPONSE.fields_by_name['error_code'].enum_type = _PRESENTDATAERRORCODE
DESCRIPTOR.message_types_by_name['OpenChannelRequest'] = _OPENCHANNELREQUEST
DESCRIPTOR.message_types_by_name['OpenChannelResponse'] = _OPENCHANNELRESPONSE
DESCRIPTOR.message_types_by_name['HeartbeatMessage'] = _HEARTBEATMESSAGE
DESCRIPTOR.message_types_by_name['Coordinate'] = _COORDINATE
DESCRIPTOR.message_types_by_name['Rectangle_Attr'] = _RECTANGLE_ATTR
DESCRIPTOR.message_types_by_name['PresentImageRequest'] = _PRESENTIMAGEREQUEST
DESCRIPTOR.message_types_by_name['PresentImageResponse'] = _PRESENTIMAGERESPONSE
DESCRIPTOR.enum_types_by_name['OpenChannelErrorCode'] = _OPENCHANNELERRORCODE
DESCRIPTOR.enum_types_by_name['ChannelContentType'] = _CHANNELCONTENTTYPE
DESCRIPTOR.enum_types_by_name['ImageFormat'] = _IMAGEFORMAT
DESCRIPTOR.enum_types_by_name['PresentDataErrorCode'] = _PRESENTDATAERRORCODE
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
OpenChannelRequest = _reflection.GeneratedProtocolMessageType('OpenChannelRequest', (_message.Message,), dict(
DESCRIPTOR = _OPENCHANNELREQUEST,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.OpenChannelRequest)
))
_sym_db.RegisterMessage(OpenChannelRequest)
OpenChannelResponse = _reflection.GeneratedProtocolMessageType('OpenChannelResponse', (_message.Message,), dict(
DESCRIPTOR = _OPENCHANNELRESPONSE,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.OpenChannelResponse)
))
_sym_db.RegisterMessage(OpenChannelResponse)
HeartbeatMessage = _reflection.GeneratedProtocolMessageType('HeartbeatMessage', (_message.Message,), dict(
DESCRIPTOR = _HEARTBEATMESSAGE,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.HeartbeatMessage)
))
_sym_db.RegisterMessage(HeartbeatMessage)
Coordinate = _reflection.GeneratedProtocolMessageType('Coordinate', (_message.Message,), dict(
DESCRIPTOR = _COORDINATE,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.Coordinate)
))
_sym_db.RegisterMessage(Coordinate)
Rectangle_Attr = _reflection.GeneratedProtocolMessageType('Rectangle_Attr', (_message.Message,), dict(
DESCRIPTOR = _RECTANGLE_ATTR,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.Rectangle_Attr)
))
_sym_db.RegisterMessage(Rectangle_Attr)
PresentImageRequest = _reflection.GeneratedProtocolMessageType('PresentImageRequest', (_message.Message,), dict(
DESCRIPTOR = _PRESENTIMAGEREQUEST,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.PresentImageRequest)
))
_sym_db.RegisterMessage(PresentImageRequest)
PresentImageResponse = _reflection.GeneratedProtocolMessageType('PresentImageResponse', (_message.Message,), dict(
DESCRIPTOR = _PRESENTIMAGERESPONSE,
__module__ = 'presenter_message_pb2'
# @@protoc_insertion_point(class_scope:ascend.presenter.proto.PresentImageResponse)
))
_sym_db.RegisterMessage(PresentImageResponse)
# @@protoc_insertion_point(module_scope)
@@ -0,0 +1,463 @@
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter socket server module"""
import threading
import select
import struct
import logging
import socket
from google.protobuf.message import DecodeError
import common.presenter_message_pb2 as pb2
from common.channel_manager import ChannelManager
from common.channel_handler import ChannelHandler
#read nothing from socket.recv()
SOCK_RECV_NULL = b''
# epool will return if no event coming in 1 s
EPOLL_TIMEOUT = 1
# it specifies the number of unaccepted connections that
# the system will allow before refusing new connections.
SOCKET_WAIT_QUEUE = 2
# message head length, include 4 bytes message total length
# and 1 byte message name length
MSG_HEAD_LENGTH = 5
class PresenterSocketServer():
"""a socket server communication with presenter agent.
"""
def __init__(self, server_address):
"""
Args:
server_address: server listen address,
include an ipv4 address and a port.
"""
# thread exit switch, if set true, thread must exit immediately.
self.thread_exit_switch = False
# message head length, include 4 bytes message total length
# and 1 byte message name length
self.msg_head_len = 5
self._create_socket_server(server_address)
def _create_socket_server(self, server_address):
"""
create a socket server
Args:
server_address: server listen address,
include an ipv4 address and a port.
"""
# Create a socket server.
self._sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._sock_server.bind(server_address)
self._sock_server.listen(SOCKET_WAIT_QUEUE)
self._sock_server.setblocking(False)
# Get server host name and port
host, port = self._sock_server.getsockname()[:2]
# Start presenter socket server thread.
threading.Thread(target=self._server_listen_thread).start()
# Display directly on the screen
print('Presenter socket server listen on %s:%s\n' % (host, port))
def set_exit_switch(self):
"""set switch True to stop presenter socket server thread."""
self.thread_exit_switch = True
def _read_socket(self, conn, read_len):
'''
Read fixed length data
Args:
conn: a socket connection
read_len: read fix byte.
Returns:
ret: True or False
buf: read fix byte buf.
'''
has_read_len = 0
read_buf = SOCK_RECV_NULL
total_buf = SOCK_RECV_NULL
while has_read_len != read_len:
try:
read_buf = conn.recv(read_len - has_read_len)
except socket.error:
logging.error("socket %u exception:socket.error", conn.fileno())
return False, None
if read_buf == SOCK_RECV_NULL:
return False, None
total_buf += read_buf
has_read_len = len(total_buf)
return True, total_buf
def _read_msg_head(self, sock_fileno, conns):
'''
Args:
sock_fileno: a socket fileno
conns: all socket connections which created by server.
Returns:
msg_total_len: total message length.
msg_name_len: message name length.
'''
ret, msg_head = self._read_socket(conns[sock_fileno], self.msg_head_len)
if not ret:
logging.error("socket %u receive msg head null", sock_fileno)
return None, None
# in Struct(), 'I' is unsigned int, 'B' is unsigned char
msg_head_data = struct.Struct('IB')
(msg_total_len, msg_name_len) = msg_head_data.unpack(msg_head)
msg_total_len = socket.ntohl(msg_total_len)
return msg_total_len, msg_name_len
def _read_msg_name(self, sock_fd, conns, msg_name_len):
'''
Args:
sock_fd: a socket fileno
conns: all socket connections which created by server.
msg_name_len: message name length.
Returns:
ret: True or False
msg_name: message name.
'''
ret, msg_name = self._read_socket(conns[sock_fd], msg_name_len)
if not ret:
logging.error("socket %u receive msg name null", sock_fd)
return False, None
try:
msg_name = msg_name.decode("utf-8")
except UnicodeDecodeError:
logging.error("msg name decode to utf-8 error")
return False, None
return True, msg_name
def _read_msg_body(self, sock_fd, conns, msg_body_len, msgs):
'''
Args:
sock_fd: a socket fileno
conns: all socket connections which created by server.
msg_name_len: message name length.
msgs: msg read from a socket
Returns:
ret: True or False
'''
ret, msg_body = self._read_socket(conns[sock_fd], msg_body_len)
if not ret:
logging.error("socket %u receive msg body null", sock_fd)
return False
msgs[sock_fd] = msg_body
return True
def _read_sock_and_process_msg(self, sock_fileno, conns, msgs):
'''
Args:
sock_fileno: a socket fileno, return value of socket.fileno()
conns: all socket connections registered in epoll
msgs: msg read from a socket
Returns:
ret: True or False
'''
# Step1: read msg head
msg_total_len, msg_name_len = self._read_msg_head(sock_fileno, conns)
if msg_total_len is None:
logging.error("msg_total_len is None.")
return False
# Step2: read msg name
ret, msg_name = self._read_msg_name(sock_fileno, conns, msg_name_len)
if not ret:
return ret
# Step3: read msg body
msg_body_len = msg_total_len - self.msg_head_len - msg_name_len
if msg_body_len < 0:
logging.error("msg_total_len:%u, msg_name_len:%u, msg_body_len:%u",
msg_total_len, msg_name_len, msg_body_len)
return False
ret = self._read_msg_body(sock_fileno, conns, msg_body_len, msgs)
if not ret:
return ret
# Step4: process msg
ret = self._process_msg(conns[sock_fileno], msg_name, msgs[sock_fileno])
return ret
def _process_epollin(self, sock_fileno, epoll, conns, msgs):
'''
Args:
sock_fileno: a socket fileno, return value of socket.fileno()
epoll: a set of select.epoll.
conns: all socket connections registered in epoll
msgs: msg read from a socket
'''
msgs[sock_fileno] = b''
try:
ret = self._read_sock_and_process_msg(sock_fileno, conns, msgs)
if not ret:
self._clean_connect(sock_fileno, epoll, conns, msgs)
except socket.error:
logging.error("receive socket error.")
self._clean_connect(sock_fileno, epoll, conns, msgs)
def _accept_new_socket(self, epoll, conns):
'''
Args:
epoll: a set of select.epoll.
conns: all socket connections registered in epoll
'''
try:
new_conn, address = self._sock_server.accept()
new_conn.setblocking(True)
epoll.register(new_conn.fileno(), select.EPOLLIN | select.EPOLLHUP)
conns[new_conn.fileno()] = new_conn
logging.info("create new connection:client-ip:%s, client-port:%s, fd:%s",
address[0], address[1], new_conn.fileno())
except socket.error:
logging.error("socket.error exception when sock.accept()")
def _server_listen_thread(self):
"""socket server thread, epoll listening all the socket events"""
epoll = select.epoll()
epoll.register(self._sock_server.fileno(), select.EPOLLIN | select.EPOLLHUP)
try:
conns = {}
msgs = {}
while True:
# thread must exit immediately
if self.thread_exit_switch:
break
events = epoll.poll(EPOLL_TIMEOUT)
# timeout, but no event come, continue waiting
if not events:
continue
for sock_fileno, event in events:
# new connection request from presenter agent
if self._sock_server.fileno() == sock_fileno:
self._accept_new_socket(epoll, conns)
# remote connection closed
# it means presenter agent exit withot close socket.
elif event & select.EPOLLHUP:
logging.info("receive event EPOLLHUP")
self._clean_connect(sock_fileno, epoll, conns, msgs)
# new data coming in a socket connection
elif event & select.EPOLLIN:
self._process_epollin(sock_fileno, epoll, conns, msgs)
# receive event not recognize
else:
logging.error("not recognize event %f", event)
self._clean_connect(sock_fileno, epoll, conns, msgs)
finally:
logging.info("conns:%s", conns)
logging.info("presenter server listen thread exit.")
epoll.unregister(self._sock_server.fileno())
epoll.close()
self._sock_server.close()
def _process_heartbeat(self, conn):
'''
set heartbeat
Args:
conn: a socket connection
Returns:
True: set heartbeat ok.
'''
sock_fileno = conn.fileno()
handler = self.channel_manager.get_channel_handler_by_fd(sock_fileno)
if handler is not None:
handler.set_heartbeat()
return True
def _process_open_channel(self, conn, msg_data):
"""
Deserialization protobuf and process open_channel request
Args:
conn: a socket connection
msg_data: a protobuf struct, include open channel request.
Returns:
protobuf structure like this:
----------------------------------------------
|channel_name | string |
|----------------------------------------------
|content_type | ChannelContentType |
|----------------------------------------------
enum ChannelContentType {
kChannelContentTypeImage = 0;
kChannelContentTypeVideo = 1;
}
"""
request = pb2.OpenChannelRequest()
response = pb2.OpenChannelResponse()
try:
request.ParseFromString(msg_data)
except DecodeError:
logging.error("ParseFromString exception: Error parsing message")
channel_name = "unknown channel"
return self._response_open_channel(conn, channel_name, response,
pb2.kOpenChannelErrorOther)
channel_name = request.channel_name
# check channel name if exist
if not self.channel_manager.is_channel_exist(channel_name):
logging.error("channel name %s is not exist.", channel_name)
# if channel is not exist, need to create the channel
ret = self.channel_manager.register_one_channel(channel_name)
if ret != ChannelManager.err_code_ok:
logging.error("Create the channel %s failed!, and ret is %d", channel_name, ret)
err_code = pb2.kOpenChannelErrorOther
self._response_open_channel(conn, channel_name, response, err_code)
# check channel path if busy
if self.channel_manager.is_channel_busy(channel_name):
logging.error("channel path %s is busy.", channel_name)
err_code = pb2.kOpenChannelErrorChannelAlreadyOpened
return self._response_open_channel(conn, channel_name, response,
err_code)
# if channel type is image, need clean image if exist
self.channel_manager.clean_channel_image(channel_name)
if request.content_type == pb2.kChannelContentTypeImage:
media_type = "image"
elif request.content_type == pb2.kChannelContentTypeVideo:
media_type = "video"
else:
logging.error("media type %s is not recognized.",
request.content_type)
return self._response_open_channel(conn, channel_name, response,
pb2.kOpenChannelErrorOther)
handler = ChannelHandler(channel_name, media_type)
self.channel_manager.create_channel_resource(
channel_name, conn.fileno(), media_type, handler)
return self._response_open_channel(conn, channel_name, response,
pb2.kOpenChannelErrorNone)
def _response_open_channel(self, conn, channel_name, response, err_code):
"""
Assemble protobuf to response open_channel request
Args:
conn: a socket connection
channel_name: name of a channel.
response: a protobuf response to presenter agent
err_code: part of the response
Returns:
ret_code:True or False
Message structure like this:
--------------------------------------------------------------------
|total message len | int | 4 bytes |
|-------------------------------------------------------------------
|message name len | byte | 1 byte |
|-------------------------------------------------------------------
|message name | string | xx bytes |
|-------------------------------------------------------------------
|message body | protobuf | xx bytes |
--------------------------------------------------------------------
protobuf structure like this:
--------------------------------------------------------------------
|error_code | enum | OpenChannelErrorCode |
|-------------------------------------------------------------------
|error_message | string | xx bytes |
|-------------------------------------------------------------------
enum OpenChannelErrorCode {
kOpenChannelErrorNone = 0;
kOpenChannelErrorNoSuchChannel = 1;
kOpenChannelErrorChannelAlreadyOpened = 2;
kOpenChannelErrorOther = -1;
}
"""
response.error_code = err_code
ret_code = False
if err_code == pb2.kOpenChannelErrorNoSuchChannel:
response.error_message = "channel {} not exist." \
.format(channel_name)
elif err_code == pb2.kOpenChannelErrorChannelAlreadyOpened:
response.error_message = "channel {} is busy.".format(channel_name)
elif err_code == pb2.kOpenChannelErrorNone:
response.error_message = "open channel succeed"
ret_code = True
else:
response.error_message = "Unknown err open channel {}." \
.format(channel_name)
self.send_message(conn, response, pb2._OPENCHANNELRESPONSE.full_name)
return ret_code
def send_message(self, conn, protobuf, msg_name):
'''
API for send message
Args:
conn: a socket connection.
protobuf: message body defined in protobuf.
msg_name: msg name.
Returns: NA
'''
message_data = protobuf.SerializeToString()
message_len = len(message_data)
msg_name_size = len(msg_name)
msg_total_size = self.msg_head_len + msg_name_size + message_len
# in Struct(), 'I' is unsigned int, 'B' is unsigned char
s = struct.Struct('IB')
msg_head = (socket.htonl(msg_total_size), msg_name_size)
packed_msg_head = s.pack(*msg_head)
msg_data = packed_msg_head + \
bytes(msg_name, encoding="utf-8") + message_data
conn.sendall(msg_data)
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# =======================================================================
#
# Copyright (C) 2018, Hisilicon Technologies Co., Ltd. All Rights Reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1 Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2 Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3 Neither the names of the copyright holders nor the names of the
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# =======================================================================
#
"""presenter server module"""
import os
import sys
import signal
import argparse
import logging
WEB_SERVER = None
APP_SERVER = None
RUN_SERVER = None
SERVER_TYPE = ""
USAGE_INFO = "python3 prensenter_server.py [-h] --app \n\t\t\t\t{body_pose}"
BODY_MAP = {"web_server": "body_pose.src.web",
"app_server": "body_pose.src.body_pose_server"
}
APP_CONF_MAP = {"body_pose": BODY_MAP}
def arg_parse():
'''arg_parse'''
global WEB_SERVER
global APP_SERVER
global SERVER_TYPE
parser = argparse.ArgumentParser(usage=USAGE_INFO)
parser.add_argument('--app', type=str, required=True,
choices=['body_pose'],
help="Application type corresponding to Presenter Server.")
args = parser.parse_args()
SERVER_TYPE = args.app
app_conf = APP_CONF_MAP.get(SERVER_TYPE)
WEB_SERVER = __import__(app_conf.get("web_server"), fromlist=True)
APP_SERVER = __import__(app_conf.get("app_server"), fromlist=True)
def start_app():
global RUN_SERVER
# start socket server for presenter agent communication
RUN_SERVER = APP_SERVER.run()
if RUN_SERVER is None:
return False
logging.info("presenter server starting, type: %s", SERVER_TYPE)
# start web ui
return WEB_SERVER.start_webapp()
def stop_app():
WEB_SERVER.stop_webapp()
RUN_SERVER.stop_thread()
def close_all_thread(signum, frame):
'''close all thread of the process, and exit.'''
logging.info("receive signal, signum:%s, frame:%s", signum, frame)
stop_app()
logging.info("presenter server exit by Ctrl + c")
sys.exit()
def check_server_exist():
pid = os.getpid()
cmd = "ps -ef|grep -v {}|grep -w presenter_server|grep {}" \
.format(pid, SERVER_TYPE)
ret = os.system(cmd)
return ret
def main_process():
'''Main function entrance'''
arg_parse()
if check_server_exist() == 0:
print("Presenter Server type \"%s\" already exist!" %(SERVER_TYPE))
return True
# process signal, when receive "Ctrl + c" signal,
# stop all thead and exit the progress.
signal.signal(signal.SIGINT, close_all_thread)
signal.signal(signal.SIGTERM, close_all_thread)
start_app()
return True
if __name__ == "__main__":
main_process()
+2
View File
@@ -0,0 +1,2 @@
tornado == 5.1.0
protobuf == 3.11.3