upload
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 580 B |
@@ -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>
|
||||
@@ -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]
|
||||
@@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -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() } } })();
|
||||
@@ -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> 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>
|
||||
@@ -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))]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,2 @@
|
||||
tornado == 5.1.0
|
||||
protobuf == 3.11.3
|
||||