Commit initial

This commit is contained in:
2020-05-15 12:27:34 +02:00
commit 70f72f2e54
87 changed files with 12684 additions and 0 deletions

View File

@ -0,0 +1,7 @@
class Camera:
def __init__(self, id, product_key, available, enabled, rtmp_handle):
self.id = id
self.product_key = product_key
self.available = available
self.enabled = enabled
self.rtmp_handle = rtmp_handle

View File

@ -0,0 +1,33 @@
class CameraCommander:
class InvalidStateException(Exception):
pass
Cameras = []
def Register(context, camera):
for c in CameraCommander.Cameras:
if c.camera.id == camera.id:
print("[CameraCommander] : Camera ({}) already registered".format(camera.id))
raise CameraCommander.InvalidStateException()
CameraCommander.Cameras.append(context)
print("[CameraCommander] : Camera ({}) registered".format(camera.id))
def GetContext(camera):
for c in CameraCommander.Cameras:
if c.camera.id == camera.id:
return c
return None
def IsRegistered(context):
return context in CameraCommander.Cameras
def Remove(context):
CameraCommander.Cameras.remove(context)
print("[CameraCommander] : Camera ({}) unregistered".format(context.camera.id))

View File

@ -0,0 +1,32 @@
import socket
class CameraConnection:
def __init__(self, sock, addr):
self.sock = sock
self.addr = addr[0]
self.port = addr[1]
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 300)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30)
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 4)
def receive(self):
data = b""
ret = self.sock.recv(1)
while ret and ret != b"\x00":
data = data + ret
ret = self.sock.recv(1)
if not data:
return None
return data.decode()
def send(self, msg):
data = (msg + "\0").encode()
return self.sock.send(data) == len(data)
def close(self):
self.sock.close()

View File

@ -0,0 +1,86 @@
from CameraModel import CameraModel
from CameraCommander import CameraCommander
from WaitAuthentication import WaitAuthentication
from WaitEnabledModeReply import WaitEnabledModeReply
from WaitDisabledModeReply import WaitDisabledModeReply
from WaitDisconnectedReply import WaitDisconnectedReply
from DeadState import DeadState
class CameraContext:
class InvalidStateException(Exception):
pass
def __init__(self, connection):
self.camera = None
self.connection = connection
self.state = WaitAuthentication()
def handle(self):
return self.state.handle(self)
def tic(self):
self.state.tic(self)
def enable(self, userCommand = None):
if not self.camera or not self.connection:
raise CameraContext.InvalidStateException()
if not self.connection.send("enable"):
print("[CameraContext] : Connection lost with camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.close()
if userCommand:
userCommand.callback(False, "connection_lost")
return False
print("[CameraContext] : Ask for enabling RTMP streaming on camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.state = WaitEnabledModeReply(userCommand)
return True
def disable(self, userCommand = None):
if not self.camera or not self.connection:
raise CameraContext.InvalidStateException()
if not self.connection.send("disable"):
print("[CameraContext] : Connection lost with camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.close()
if userCommand:
userCommand.callback(False, "connection_lost")
return False
print("[CameraContext] : Ask for disabling RTMP streaming on camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.state = WaitDisabledModeReply(userCommand)
return True
def disconnect(self, userCommand = None):
if not self.camera or not self.connection:
raise CameraContext.InvalidStateException()
if not self.connection.send("disconnect"):
print("[CameraContext] : Connection lost with camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.close()
if userCommand:
userCommand.callback(False, "connection_lost")
return False
print("[CameraContext] : Ask for disconnecting camera ({}) at {}:{}".format(self.camera.id, self.connection.addr, self.connection.port))
self.state = WaitDisconnectedReply(userCommand)
return True
def close(self):
if not self.connection:
raise CameraContext.InvalidStateException()
if CameraCommander.IsRegistered(self):
CameraCommander.Remove(self)
if not self.camera:
raise CameraContext.InvalidStateException()
mdl = CameraModel()
self.camera.available = False
self.camera.rtmp_handle = ""
mdl.update(self.camera)
self.camera = None
print("[CameraContext] : Close connection with {}:{}".format(self.connection.addr, self.connection.port))
self.connection.close()
self.connection = None
self.state = DeadState()

View File

@ -0,0 +1,24 @@
import DataBase
from Camera import Camera
class CameraModel:
def getById(self, id):
db = DataBase.GetConnection()
cursor = db.cursor()
cursor.execute("""SELECT * FROM camera WHERE id=%s""", (id,))
row = cursor.fetchone()
cursor.close()
db.close()
if row:
return Camera(row[0], row[1], bool(row[2]), bool(row[3]), row[4])
return None
def update(self, camera):
db = DataBase.GetConnection()
cursor = db.cursor()
cursor.execute("""UPDATE camera SET available=%s, enabled=%s, rtmp_handle=%s WHERE id=%s""", (int(camera.available), int(camera.enabled), camera.rtmp_handle, camera.id))
cursor.close()
db.commit()
db.close()

View File

@ -0,0 +1,8 @@
import mysql.connector
def GetConnection():
try:
return mysql.connector.connect(user = "camplatform_user", password = "lLZwfSLuwye3qW4r", host = "public.lan", database = "camplatform_db")
except mysql.connector.Error:
return None

View File

@ -0,0 +1,10 @@
from WaitState import WaitState
class DeadState(WaitState):
def handle(self, context):
pass
def tic(self, context):
pass

View File

@ -0,0 +1,5 @@
class User:
def __init__(self, id, login, password):
self.id = id
self.login = login
self.password = password

View File

@ -0,0 +1,120 @@
from UserModel import UserModel
from CameraModel import CameraModel
import CameraCommander
from WaitActivity import WaitActivity
class UserCommand:
class InvalidStateException(Exception):
pass
def __init__(self, sock, addr):
self.sock = sock
self.addr = addr[0]
self.port = addr[1]
def receive(self):
data = b""
ret = self.sock.recv(1)
while ret and ret != b"\x00":
data = data + ret
ret = self.sock.recv(1)
if not data:
return None
return data.decode()
def send(self, msg):
data = (msg + "\0").encode()
return self.sock.send(data) == len(data)
def close(self):
print("[UserCommand] : Closing connection with {}:{}".format(self.addr, self.port))
self.sock.close()
self.sock = None
self.addr = None
self.port = None
def callback(self, result, error_msg = None):
print("[UserCommand] : Send reply to {}:{}".format(self.addr, self.port))
if result:
self.send("ok")
elif error_msg:
self.send("error:" + error_msg)
else:
self.send("error")
self.close()
def execute(self):
if not self.sock:
raise UserCommand.InvalidStateException()
data = self.receive()
if not data:
print("[UserCommand] : Connection lost with {}:{}".format(self.addr, self.port))
self.close()
return
words = data.split(":")
if len(words) != 4:
print("[UserCommand] : Error from {}:{}, invalid data".format(self.addr, self.port))
self.send("error:invalid_data")
self.close()
return
userMdl = UserModel()
user = userMdl.getByLogin(words[0])
if not user:
print("[UserCommand] : Error from {}:{}, invalid username".format(self.addr, self.port))
self.send("error:invalid_username")
self.close()
return
if user.password != words[1]:
print("[UserCommand] : Error from {}:{}, invalid password for user ({})".format(self.addr, self.port, user.login))
self.send("error:invalid_password")
self.close()
return
camMdl = CameraModel()
camera = camMdl.getById(words[3])
if not camera:
print("[UserCommand] : Error from user ({}) at {}:{}, invalid camera's serial number".format(user.login, self.addr, self.port))
self.send("error:invalid_camera_id")
self.close()
return
if not userMdl.haveCamera(user, camera):
print("[UserCommand] : User ({}) at {}:{} is not allowed to command camera ({})".format(user.login, self.addr, self.port, camera.id))
self.send("error:not_authorized")
self.close()
return
context = CameraCommander.CameraCommander.GetContext(camera)
if not context:
print("[UserCommand] : Error from user ({}) at {}:{}, camera ({}) is not registered".format(user.login, self.addr, self.port, camera.id))
self.send("error:camera_not_registered")
self.close()
return
if not isinstance(context.state, WaitActivity):
print("[UserCommand] : Error from user ({}) at {}:{}, camera ({}) is busy".format(user.login, self.addr, self.port, camera.id))
self.send("error:camera_busy")
self.close()
return
if words[2] == "enable":
context.enable(self)
elif words[2] == "disable":
context.disable(self)
elif words[2] == "disconnect":
context.disconnect(self)
else:
print("[UserCommand] : Error from user ({}) at {}:{}, invalid command".format(user.login, self.addr, self.port))
self.send("error:invalid_command")
self.close()
return

View File

@ -0,0 +1,28 @@
import DataBase
from User import User
class UserModel:
def getByLogin(self, login):
db = DataBase.GetConnection()
cursor = db.cursor()
cursor.execute("""SELECT * FROM user WHERE login=%s""", (login,))
row = cursor.fetchone()
cursor.close()
db.close()
if row:
return User(row[0], row[1], row[2])
return None
def haveCamera(self, user, camera):
db = DataBase.GetConnection()
cursor = db.cursor()
cursor.execute("""SELECT * FROM user_camera WHERE user_id=%s AND camera_id=%s""", (user.id, camera.id))
row = cursor.fetchone()
cursor.close()
db.close()
if row:
return True
return False

View File

@ -0,0 +1,14 @@
from WaitState import WaitState
from CameraCommander import CameraCommander
class WaitActivity(WaitState):
def handle(self, context):
data = self.waitMsg(context)
if data:
print("[WaitActivity] : Error({}) from camera ({}) at {}:{}".format(data, context.camera.id, context.connection.addr, context.connection.port))
context.close()
def tic(self, context):
pass

View File

@ -0,0 +1,55 @@
import random
from CameraCommander import CameraCommander
from CameraModel import CameraModel
from WaitState import WaitState
from WaitActivity import WaitActivity
import CameraContext
class WaitAuthentication(WaitState):
def handle(self, context):
data = self.waitMsg(context)
if not data:
return
words = data.split(":")
if len(words) != 2:
print("[WaitAuthentication] : Invalid data from camera at {}:{}".format(context.connection.addr, context.connection.port))
context.connection.send("invalid_data")
context.close()
return
mdl = CameraModel()
camera = mdl.getById(words[0])
if not camera or camera.product_key != words[1]:
print("[WaitAuthentication] : Authentication failed for camera at {}:{}".format(context.connection.addr, context.connection.port))
context.connection.send("refused")
context.close()
return
try:
CameraCommander.Register(context, camera)
except CameraCommander.InvalidStateException:
context.connection.send("refused")
context.close()
return
context.camera = camera
print("[WaitAuthentication] : Camera at {}:{} successfully authenticated as ({})".format(context.connection.addr, context.connection.port, context.camera.id))
context.camera.available = True
context.camera.rtmp_handle = hex(hash(random.random()))[2:]
mdl.update(context.camera)
if not context.connection.send("accepted:{}".format(context.camera.rtmp_handle)):
print("[WaitAuthentication] : Connection lost with camera ({}) at {}:{}".format(context.camera.id, context.connection.addr, context.connection.port))
context.close()
return
if context.camera.enabled:
context.enable()
return
context.state = WaitActivity()
def tic(self, context):
pass

View File

@ -0,0 +1,40 @@
import time
from CameraModel import CameraModel
from WaitState import WaitState
from WaitActivity import WaitActivity
class WaitDisabledModeReply(WaitState):
def __init__(self, userCommand):
self.userCommand = userCommand
self.initTime = time.time()
def handle(self, context):
data = self.waitMsg(context)
if not data:
if self.userCommand:
self.userCommand.callback(False, "connection_lost")
return
if data == "disabled":
print("[WaitDisabledModeReply] : Camera ({}) at {}:{} successfully disabled".format(context.camera.id, context.connection.addr, context.connection.port))
context.camera.enabled = False
mdl = CameraModel()
mdl.update(context.camera)
context.state = WaitActivity()
if self.userCommand:
self.userCommand.callback(True)
else:
print("[WaitDisabledModeReply] : Error({}) from camera ({}) at {}:{}".format(data, context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, data)
def tic(self, context):
if time.time() - self.initTime > 10:
print("[WaitDisabledModeReply] : Timeout exeeded for camera ({}) at {}:{}".format(context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, "timeout")

View File

@ -0,0 +1,35 @@
import time
from WaitState import WaitState
class WaitDisconnectedReply(WaitState):
def __init__(self, userCommand):
self.userCommand = userCommand
self.initTime = time.time()
def handle(self, context):
data = self.waitMsg(context)
if not data:
if self.userCommand:
self.userCommand.callback(False, "connection_lost")
return
if data == "disconnected":
print("[CameraContext] : Camera ({}) at {}:{} successfully disconnected".format(context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(True)
else:
print("[WaitDisconnectedReply] : Error({}) from camera ({}) at {}:{}".format(data, context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, data)
def tic(self, context):
if time.time() - self.initTime > 10:
print("[WaitEnabledModeReply] : Timeout exeeded for camera ({}) at {}:{}".format(context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, "timeout")

View File

@ -0,0 +1,40 @@
import time
from CameraModel import CameraModel
from WaitState import WaitState
from WaitActivity import WaitActivity
class WaitEnabledModeReply(WaitState):
def __init__(self, userCommand):
self.userCommand = userCommand
self.initTime = time.time()
def handle(self, context):
data = self.waitMsg(context)
if not data:
if self.userCommand:
self.userCommand.callback(False, "connection_lost")
return
if data == "enabled":
print("[WaitEnabledModeReply] : Camera ({}) at {}:{} successfully enabled".format(context.camera.id, context.connection.addr, context.connection.port))
context.camera.enabled = True
mdl = CameraModel()
mdl.update(context.camera)
context.state = WaitActivity()
if self.userCommand:
self.userCommand.callback(True)
else:
print("[WaitEnabledModeReply] : Error({}) from camera ({}) at {}:{}".format(data, context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, data)
def tic(self, context):
if time.time() - self.initTime > 10:
print("[WaitEnabledModeReply] : Timeout exeeded for camera ({}) at {}:{}".format(context.camera.id, context.connection.addr, context.connection.port))
context.close()
if self.userCommand:
self.userCommand.callback(False, "timeout")

View File

@ -0,0 +1,17 @@
class WaitState:
def waitMsg(self, context):
data = context.connection.receive()
if not data:
print("[WaitState] : Connection lost with camera at {}:{}".format(context.connection.addr, context.connection.port))
context.close()
return None
else:
return data
def handle(self, context):
raise NotImplementedError()
def tic(self, context):
raise NotImplementedError()

View File

@ -0,0 +1,102 @@
import socket
import select
import signal
from CameraContext import CameraContext
from UserCommand import UserCommand
from CameraConnection import CameraConnection
from CameraCommander import CameraCommander
from WaitActivity import WaitActivity
def on_sigint(signal, frame):
global server_enabled
server_enabled = False
#
# Main
#
server_enabled = True
signal.signal(signal.SIGINT, on_sigint)
connected_cameras = []
connected_users = []
cam_listener_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cam_listener_sock.bind(("", 41551))
cam_listener_sock.listen(5)
cmd_listener_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
cmd_listener_sock.bind(("", 40550))
cmd_listener_sock.listen(5)
print("[Main] : Ready")
while server_enabled or len(CameraCommander.Cameras) > 0:
if server_enabled:
conn_list, wlist, xlist = select.select([cam_listener_sock, cmd_listener_sock], [], [], 0.05)
if cam_listener_sock in conn_list:
cam_sock, cam_addr = cam_listener_sock.accept()
connected_cameras.append(CameraContext(CameraConnection(cam_sock, cam_addr)))
elif cmd_listener_sock in conn_list:
user_sock, user_addr = cmd_listener_sock.accept()
connected_users.append(UserCommand(user_sock, user_addr))
new_connected_users = []
for userCommand in connected_users:
if userCommand.sock:
new_connected_users.append(userCommand)
connected_users = new_connected_users
user_sock_list = [userCommand.sock for userCommand in connected_users]
if server_enabled:
sock_read_list, wlist, xlist = select.select(user_sock_list, [], [], 0.05)
for user_sock in sock_read_list:
for userCommand in connected_users:
if user_sock == userCommand.sock:
userCommand.execute()
new_connected_cameras = []
for context in connected_cameras:
if context.connection:
new_connected_cameras.append(context)
connected_cameras = new_connected_cameras
if not server_enabled:
for context in CameraCommander.Cameras:
if isinstance(context.state, WaitActivity):
try:
context.disconnect()
except CameraContext.TransmissionErrorException:
context.close()
cam_sock_list = [context.connection.sock for context in connected_cameras]
sock_read_list, wlist, xlist = select.select(cam_sock_list, [], [], 0.05)
for cam_sock in sock_read_list:
for context in connected_cameras:
if cam_sock == context.connection.sock:
context.handle()
for context in connected_cameras:
context.tic()
for context in connected_cameras:
if context.connection:
context.close()
for userCommand in connected_users:
if userCommand.sock:
userCommand.close()
cmd_listener_sock.shutdown(socket.SHUT_RDWR)
cmd_listener_sock.close()
cam_listener_sock.shutdown(socket.SHUT_RDWR)
cam_listener_sock.close()
print("[Main] : Bye !!")