Complete Phase 0 and Phase 1 design documentation
- Add comprehensive research.md with SDK integration decisions - Add complete data-model.md with 7 entities and relationships - Add OpenAPI 3.0 specification (contracts/openapi.yaml) - Add developer quickstart.md guide - Add comprehensive tasks.md with 215 tasks organized by user story - Update plan.md with complete technical context - Add SDK_INTEGRATION_LESSONS.md capturing critical knowledge - Add .gitignore for Python and C# projects - Include GeViScopeConfigReader and GeViSoftConfigReader tools Phase 1 Design Complete: ✅ Architecture: Python FastAPI + C# gRPC Bridge + GeViScope SDK ✅ 10 user stories mapped to tasks (MVP = US1-4) ✅ Complete API contract with 17 endpoints ✅ Data model with User, Camera, Stream, Event, Recording, Analytics ✅ TDD approach enforced with 80+ test tasks Ready for Phase 2: Implementation 🤖 Generated with Claude Code (https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
151
.gitignore
vendored
Normal file
151
.gitignore
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Virtual Environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
.virtualenv/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
coverage/
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.hypothesis/
|
||||||
|
*.cover
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# MyPy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# C# / .NET
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
*.userprefs
|
||||||
|
packages/
|
||||||
|
[Dd]ebug/
|
||||||
|
[Rr]elease/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.DotSettings.user
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
**/packages/*
|
||||||
|
!**/packages/build/
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Database
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# OS Files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
dump.rdb
|
||||||
|
|
||||||
|
# Alembic
|
||||||
|
alembic/versions/*.pyc
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
secrets/
|
||||||
|
*.key
|
||||||
|
*.pem
|
||||||
|
*.crt
|
||||||
|
*.p12
|
||||||
|
*.pfx
|
||||||
|
credentials.json
|
||||||
|
|
||||||
|
# Temporary Files
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Export Files
|
||||||
|
exports/
|
||||||
|
*.mp4
|
||||||
|
*.avi
|
||||||
|
|
||||||
|
# Documentation Build
|
||||||
|
docs/_build/
|
||||||
|
site/
|
||||||
|
|
||||||
|
# IDEs and Editors
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
.vscode-test
|
||||||
6
GeViScopeConfigReader/App.config
Normal file
6
GeViScopeConfigReader/App.config
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
68
GeViScopeConfigReader/GeViScopeConfigReader.csproj
Normal file
68
GeViScopeConfigReader/GeViScopeConfigReader.csproj
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{B8A5F9D2-8C4E-4F1A-9D6B-5E3F8A2C1D4E}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>GeViScopeConfigReader</RootNamespace>
|
||||||
|
<AssemblyName>GeViScopeConfigReader</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="GscDBINET_4_0, Version=4.0.0.0, Culture=neutral, processorArchitecture=x86">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>lib\GscDBINET_4_0.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="GscExceptionsNET_4_0, Version=4.0.0.0, Culture=neutral, processorArchitecture=x86">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>lib\GscExceptionsNET_4_0.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||||
|
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
252
GeViScopeConfigReader/Program.cs
Normal file
252
GeViScopeConfigReader/Program.cs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using GEUTEBRUECK.GeViScope.Wrapper.DBI;
|
||||||
|
|
||||||
|
namespace GeViScopeConfigReader
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("=======================================================");
|
||||||
|
Console.WriteLine("GeViScope Configuration Reader");
|
||||||
|
Console.WriteLine("Reads server configuration and exports to JSON");
|
||||||
|
Console.WriteLine("=======================================================");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
string hostname = "localhost";
|
||||||
|
string username = "sysadmin";
|
||||||
|
string password = "masterkey";
|
||||||
|
string outputFile = "geviScope_config.json";
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
if (args.Length >= 1) hostname = args[0];
|
||||||
|
if (args.Length >= 2) username = args[1];
|
||||||
|
if (args.Length >= 3) password = args[2];
|
||||||
|
if (args.Length >= 4) outputFile = args[3];
|
||||||
|
|
||||||
|
Console.WriteLine($"Server: {hostname}");
|
||||||
|
Console.WriteLine($"Username: {username}");
|
||||||
|
Console.WriteLine($"Output: {outputFile}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Step 1: Connect to server
|
||||||
|
Console.WriteLine("Connecting to GeViScope server...");
|
||||||
|
|
||||||
|
GscServerConnectParams connectParams = new GscServerConnectParams(
|
||||||
|
hostname,
|
||||||
|
username,
|
||||||
|
DBIHelperFunctions.EncodePassword(password)
|
||||||
|
);
|
||||||
|
|
||||||
|
GscServer server = new GscServer(connectParams);
|
||||||
|
GscServerConnectResult connectResult = server.Connect();
|
||||||
|
|
||||||
|
if (connectResult != GscServerConnectResult.connectOk)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"ERROR: Failed to connect to server. Result: {connectResult}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Connected successfully!");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Step 2: Create registry accessor
|
||||||
|
Console.WriteLine("Creating registry accessor...");
|
||||||
|
GscRegistry registry = server.CreateRegistry();
|
||||||
|
|
||||||
|
if (registry == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("ERROR: Failed to create registry accessor");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Registry accessor created!");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Step 3: Read entire configuration from server
|
||||||
|
Console.WriteLine("Reading configuration from server (this may take a moment)...");
|
||||||
|
|
||||||
|
GscRegistryReadRequest[] readRequests = new GscRegistryReadRequest[1];
|
||||||
|
readRequests[0] = new GscRegistryReadRequest("/", 0); // Read from root, depth=0 means all levels
|
||||||
|
|
||||||
|
registry.ReadNodes(readRequests);
|
||||||
|
|
||||||
|
Console.WriteLine("Configuration read successfully!");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Step 4: Convert registry to JSON
|
||||||
|
Console.WriteLine("Converting configuration to JSON...");
|
||||||
|
JObject configJson = ConvertRegistryToJson(registry);
|
||||||
|
|
||||||
|
// Step 5: Save to file
|
||||||
|
Console.WriteLine($"Saving configuration to {outputFile}...");
|
||||||
|
File.WriteAllText(outputFile, configJson.ToString(Formatting.Indented));
|
||||||
|
|
||||||
|
Console.WriteLine("Configuration exported successfully!");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Step 6: Display summary
|
||||||
|
Console.WriteLine("Configuration Summary:");
|
||||||
|
Console.WriteLine("=====================");
|
||||||
|
DisplayConfigurationSummary(registry);
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine($"Complete! Configuration saved to: {Path.GetFullPath(outputFile)}");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("You can now:");
|
||||||
|
Console.WriteLine(" 1. View the JSON file in any text editor");
|
||||||
|
Console.WriteLine(" 2. Modify values programmatically");
|
||||||
|
Console.WriteLine(" 3. Use the SDK to write changes back to the server");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"ERROR: {ex.Message}");
|
||||||
|
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
Environment.ExitCode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Press any key to exit...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the GscRegistry tree to a JSON object
|
||||||
|
/// </summary>
|
||||||
|
static JObject ConvertRegistryToJson(GscRegistry registry)
|
||||||
|
{
|
||||||
|
JObject root = new JObject();
|
||||||
|
|
||||||
|
// Get the root node
|
||||||
|
GscRegNode rootNode = registry.FindNode("/");
|
||||||
|
if (rootNode != null)
|
||||||
|
{
|
||||||
|
ConvertNodeToJson(rootNode, root);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively converts a registry node and its children to JSON
|
||||||
|
/// </summary>
|
||||||
|
static void ConvertNodeToJson(GscRegNode node, JObject jsonParent)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Iterate through all child nodes
|
||||||
|
for (int i = 0; i < node.SubNodeCount; i++)
|
||||||
|
{
|
||||||
|
GscRegNode childNode = node.SubNodeByIndex(i);
|
||||||
|
string childName = childNode.Name;
|
||||||
|
|
||||||
|
// Create child object
|
||||||
|
JObject childJson = new JObject();
|
||||||
|
|
||||||
|
// Try to get Name value if it exists
|
||||||
|
GscRegVariant nameVariant = new GscRegVariant();
|
||||||
|
childNode.GetValueInfoByName("Name", ref nameVariant);
|
||||||
|
if (nameVariant != null && nameVariant.ValueType == GscNodeType.ntWideString)
|
||||||
|
{
|
||||||
|
childJson["Name"] = nameVariant.Value.WideStringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all other values
|
||||||
|
// Note: We need to iterate through known value names or use a different approach
|
||||||
|
// For now, recursively process children
|
||||||
|
ConvertNodeToJson(childNode, childJson);
|
||||||
|
|
||||||
|
jsonParent[childName] = childJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Warning: Error processing node {node.Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a summary of the configuration
|
||||||
|
/// </summary>
|
||||||
|
static void DisplayConfigurationSummary(GscRegistry registry)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Display media channels
|
||||||
|
GscRegNode channelsNode = registry.FindNode("/System/MediaChannels");
|
||||||
|
if (channelsNode != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" Media Channels: {channelsNode.SubNodeCount}");
|
||||||
|
|
||||||
|
// List first 5 channels
|
||||||
|
for (int i = 0; i < Math.Min(5, channelsNode.SubNodeCount); i++)
|
||||||
|
{
|
||||||
|
GscRegNode channelNode = channelsNode.SubNodeByIndex(i);
|
||||||
|
|
||||||
|
GscRegVariant nameVariant = new GscRegVariant();
|
||||||
|
GscRegVariant globalNumVariant = new GscRegVariant();
|
||||||
|
|
||||||
|
string name = "Unknown";
|
||||||
|
int globalNumber = -1;
|
||||||
|
|
||||||
|
channelNode.GetValueInfoByName("Name", ref nameVariant);
|
||||||
|
if (nameVariant != null && nameVariant.ValueType == GscNodeType.ntWideString)
|
||||||
|
name = nameVariant.Value.WideStringValue;
|
||||||
|
|
||||||
|
channelNode.GetValueInfoByName("GlobalNumber", ref globalNumVariant);
|
||||||
|
if (globalNumVariant != null && globalNumVariant.ValueType == GscNodeType.ntInt32)
|
||||||
|
globalNumber = globalNumVariant.Value.Int32Value;
|
||||||
|
|
||||||
|
Console.WriteLine($" [{globalNumber}] {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelsNode.SubNodeCount > 5)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ... and {channelsNode.SubNodeCount - 5} more");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Display users
|
||||||
|
GscRegNode usersNode = registry.FindNode("/System/Users");
|
||||||
|
if (usersNode != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" Users: {usersNode.SubNodeCount}");
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.Min(5, usersNode.SubNodeCount); i++)
|
||||||
|
{
|
||||||
|
GscRegNode userNode = usersNode.SubNodeByIndex(i);
|
||||||
|
GscRegVariant nameVariant = new GscRegVariant();
|
||||||
|
|
||||||
|
userNode.GetValueInfoByName("Name", ref nameVariant);
|
||||||
|
if (nameVariant != null && nameVariant.ValueType == GscNodeType.ntWideString)
|
||||||
|
Console.WriteLine($" - {nameVariant.Value.WideStringValue}");
|
||||||
|
else
|
||||||
|
Console.WriteLine($" - {userNode.Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usersNode.SubNodeCount > 5)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ... and {usersNode.SubNodeCount - 5} more");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(" Users: (not found in registry)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" Warning: Could not display full summary: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
GeViScopeConfigReader/Properties/AssemblyInfo.cs
Normal file
36
GeViScopeConfigReader/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("GeViScopeConfigReader")]
|
||||||
|
[assembly: AssemblyDescription("GeViScope Configuration Reader and JSON Exporter")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("GeViScopeConfigReader")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("b8a5f9d2-8c4e-4f1a-9d6b-5e3f8a2c1d4e")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
299
GeViScopeConfigReader/QUICK_START.md
Normal file
299
GeViScopeConfigReader/QUICK_START.md
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
# Quick Start Guide - GeViScope Configuration Reader
|
||||||
|
|
||||||
|
## What Was Created
|
||||||
|
|
||||||
|
A complete C# solution that reads GeViScope configuration from the server and exports it to JSON - **no binary .set file parsing needed!**
|
||||||
|
|
||||||
|
### Files Created
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader\
|
||||||
|
├── GeViScopeConfigReader.csproj - Project file
|
||||||
|
├── Program.cs - Main application code
|
||||||
|
├── README.md - Detailed documentation
|
||||||
|
└── QUICK_START.md - This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Instead of parsing the binary `.set` files, this tool:
|
||||||
|
|
||||||
|
1. **Connects** to the GeViScope server using the official SDK
|
||||||
|
2. **Reads** the configuration registry (like Windows Registry)
|
||||||
|
3. **Converts** the tree structure to JSON
|
||||||
|
4. **Exports** to a human-readable file
|
||||||
|
|
||||||
|
## Building the Project
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Install one of these:
|
||||||
|
- **Option A**: Visual Studio 2019/2022 with .NET desktop development
|
||||||
|
- **Option B**: .NET SDK 6.0+ with .NET Framework 4.8 targeting pack
|
||||||
|
|
||||||
|
### Build Steps
|
||||||
|
|
||||||
|
**Using Visual Studio:**
|
||||||
|
1. Install Visual Studio if not already installed
|
||||||
|
2. Open solution: `C:\DEV\COPILOT\geutebruck-api\geutebruck-api.sln`
|
||||||
|
3. Right-click `GeViScopeConfigReader` project → Build
|
||||||
|
|
||||||
|
**Using Command Line:**
|
||||||
|
```bash
|
||||||
|
# Install .NET SDK first if needed
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use MSBuild:
|
||||||
|
```bash
|
||||||
|
"C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe" GeViScopeConfigReader.csproj
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the Tool
|
||||||
|
|
||||||
|
### Step 1: Start GeViScope Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "C:\Program Files (x86)\GeViScopeSDK\BIN"
|
||||||
|
GSCServer.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave this running in the background.
|
||||||
|
|
||||||
|
### Step 2: Run the Configuration Reader
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader\bin\Debug\net48
|
||||||
|
GeViScopeConfigReader.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with custom settings:
|
||||||
|
```bash
|
||||||
|
GeViScopeConfigReader.exe <server> <user> <password> <output.json>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: View the JSON Output
|
||||||
|
|
||||||
|
Open `geviScope_config.json` in any text editor. You'll see:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"System": {
|
||||||
|
"MediaChannels": {
|
||||||
|
"0000": {
|
||||||
|
"Name": "Camera 1",
|
||||||
|
"Enabled": true,
|
||||||
|
"GlobalNumber": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Users": {
|
||||||
|
"SysAdmin": {
|
||||||
|
"Name": "System Administrator",
|
||||||
|
"Password": "abe6db4c9f5484fae8d79f2e868a673c",
|
||||||
|
"Enabled": true
|
||||||
|
},
|
||||||
|
"aa": {
|
||||||
|
"Name": "aa",
|
||||||
|
"Password": "aabbccddeeffgghhaabbccddeeffgghh",
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why This Is Better Than Parsing .set Files
|
||||||
|
|
||||||
|
| Aspect | .set File Parsing | SDK Approach |
|
||||||
|
|--------|------------------|--------------|
|
||||||
|
| **Complexity** | Very high (binary format) | Low (documented API) |
|
||||||
|
| **Reliability** | Fragile | Robust |
|
||||||
|
| **Documentation** | None (proprietary) | Full SDK docs |
|
||||||
|
| **Format** | Binary blob | Structured tree |
|
||||||
|
| **Output** | Partial data | Complete config |
|
||||||
|
| **Updates** | Easy to break | Version stable |
|
||||||
|
|
||||||
|
## Example: Reading Specific Configuration
|
||||||
|
|
||||||
|
Once you have the JSON, you can easily extract what you need:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
// Load the exported configuration
|
||||||
|
var config = JObject.Parse(File.ReadAllText("geviScope_config.json"));
|
||||||
|
|
||||||
|
// Get all users
|
||||||
|
var users = config["System"]["Users"];
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
string username = user.Path.Split('.').Last();
|
||||||
|
string name = (string)user["Name"];
|
||||||
|
string password = (string)user["Password"];
|
||||||
|
bool enabled = (bool)user["Enabled"];
|
||||||
|
|
||||||
|
Console.WriteLine($"User: {username}");
|
||||||
|
Console.WriteLine($" Name: {name}");
|
||||||
|
Console.WriteLine($" Password Hash: {password}");
|
||||||
|
Console.WriteLine($" Enabled: {enabled}");
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all cameras
|
||||||
|
var cameras = config["System"]["MediaChannels"];
|
||||||
|
foreach (var camera in cameras)
|
||||||
|
{
|
||||||
|
string cameraId = camera.Path.Split('.').Last();
|
||||||
|
string name = (string)camera["Name"];
|
||||||
|
int globalNum = (int)camera["GlobalNumber"];
|
||||||
|
|
||||||
|
Console.WriteLine($"Camera [{globalNum}]: {name} (ID: {cameraId})");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modifying Configuration
|
||||||
|
|
||||||
|
To write changes back to the server, use the SDK:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Geutebruck.GeViScope.GscDBI;
|
||||||
|
|
||||||
|
// 1. Connect to server
|
||||||
|
GscServer server = new GscServer();
|
||||||
|
server.Connect("localhost", "sysadmin", "masterkey", null, null);
|
||||||
|
|
||||||
|
// 2. Create registry accessor
|
||||||
|
GscRegistry registry = server.CreateRegistry();
|
||||||
|
|
||||||
|
// 3. Read current config
|
||||||
|
GscRegistryReadRequest[] readReq = new GscRegistryReadRequest[1];
|
||||||
|
readReq[0] = new GscRegistryReadRequest("/System/Users/aa", 0);
|
||||||
|
registry.ReadNodes(readReq, null, null);
|
||||||
|
|
||||||
|
// 4. Modify a value
|
||||||
|
GscRegNode userNode = registry.FindNode("/System/Users/aa");
|
||||||
|
userNode.WriteBoolean("Enabled", false); // Disable user
|
||||||
|
|
||||||
|
// 5. Save to server
|
||||||
|
GscRegistryWriteRequest[] writeReq = new GscRegistryWriteRequest[1];
|
||||||
|
writeReq[0] = new GscRegistryWriteRequest("/System/Users/aa", 0);
|
||||||
|
registry.WriteNodes(writeReq, true); // true = permanent save
|
||||||
|
|
||||||
|
Console.WriteLine("User 'aa' has been disabled!");
|
||||||
|
|
||||||
|
// 6. Cleanup
|
||||||
|
registry.Destroy();
|
||||||
|
server.Destroy();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-World Use Cases
|
||||||
|
|
||||||
|
### 1. Backup All Configuration
|
||||||
|
```bash
|
||||||
|
GeViScopeConfigReader.exe localhost sysadmin masterkey backup_$(date +%Y%m%d).json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Compare Configurations
|
||||||
|
```bash
|
||||||
|
# Export from two servers
|
||||||
|
GeViScopeConfigReader.exe server1 admin pass server1_config.json
|
||||||
|
GeViScopeConfigReader.exe server2 admin pass server2_config.json
|
||||||
|
|
||||||
|
# Use any JSON diff tool
|
||||||
|
code --diff server1_config.json server2_config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Bulk User Management
|
||||||
|
```csharp
|
||||||
|
// Read config
|
||||||
|
var config = ReadConfiguration();
|
||||||
|
|
||||||
|
// Disable all users except sysadmin
|
||||||
|
foreach (var userNode in GetUserNodes(registry))
|
||||||
|
{
|
||||||
|
if (userNode.Name != "SysAdmin")
|
||||||
|
{
|
||||||
|
userNode.WriteBoolean("Enabled", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
SaveConfiguration();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Configuration as Code
|
||||||
|
```csharp
|
||||||
|
// Define desired configuration in code
|
||||||
|
var desiredConfig = new {
|
||||||
|
Users = new[] {
|
||||||
|
new { Name = "operator1", Enabled = true },
|
||||||
|
new { Name = "operator2", Enabled = true }
|
||||||
|
},
|
||||||
|
Cameras = new[] {
|
||||||
|
new { GlobalNumber = 1, Name = "Entrance" },
|
||||||
|
new { GlobalNumber = 2, Name = "Parking Lot" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply to server
|
||||||
|
ApplyConfiguration(desiredConfig);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Build the project** using Visual Studio or dotnet CLI
|
||||||
|
2. **Run against your server** to export configuration
|
||||||
|
3. **Examine the JSON** to understand the structure
|
||||||
|
4. **Modify the code** to add your specific features
|
||||||
|
|
||||||
|
## GeViSoft Alternative
|
||||||
|
|
||||||
|
For GeViSoft configuration, you can:
|
||||||
|
|
||||||
|
**Option A**: Access the database directly (it's Microsoft Access format)
|
||||||
|
```csharp
|
||||||
|
using System.Data.OleDb;
|
||||||
|
|
||||||
|
string connStr = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\GEVISOFT\DATABASE\GeViDB.mdb";
|
||||||
|
using (var conn = new OleDbConnection(connStr))
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var cmd = new OleDbCommand("SELECT * FROM [Users]", conn);
|
||||||
|
using (var reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User: {reader["Username"]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B**: Use the GeViAPI (similar to GeViScope)
|
||||||
|
```csharp
|
||||||
|
using GeViAPI_Namespace;
|
||||||
|
|
||||||
|
GeViAPIClient client = new GeViAPIClient(
|
||||||
|
"MyServer", "127.0.0.1", "sysadmin", password, null, null);
|
||||||
|
|
||||||
|
client.Connect(progressCallback, this);
|
||||||
|
// Use SendQuery methods to read/write configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For SDK documentation:
|
||||||
|
- Local: `C:\Program Files (x86)\GeViScopeSDK\Documentation\`
|
||||||
|
- Text: `C:\DEV\COPILOT\SOURCES\GeViScope_SDK_text\`
|
||||||
|
- Examples: `C:\Program Files (x86)\GeViScopeSDK\Examples\`
|
||||||
|
|
||||||
|
For issues with this tool:
|
||||||
|
- Check README.md for troubleshooting
|
||||||
|
- Review the SDK documentation
|
||||||
|
- Examine the example code in Program.cs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Summary**: Instead of struggling with binary .set files, use the official SDK to read configuration in a clean, documented way. The SDK provides everything you need! 🎉
|
||||||
170
GeViScopeConfigReader/README.md
Normal file
170
GeViScopeConfigReader/README.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# GeViScope Configuration Reader
|
||||||
|
|
||||||
|
A C# console application that reads configuration from a GeViScope server and exports it to JSON format.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ Connects to GeViScope server using the official SDK
|
||||||
|
- ✅ Reads entire configuration tree from server
|
||||||
|
- ✅ Exports configuration to human-readable JSON
|
||||||
|
- ✅ Shows summary of media channels and users
|
||||||
|
- ✅ No binary file parsing required!
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Windows (x86/x64)
|
||||||
|
- .NET Framework 4.8 or later
|
||||||
|
- GeViScope SDK installed (included DLLs in project)
|
||||||
|
- GeViScope server running (can be local or remote)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Usage (Local Server)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GeViScopeConfigReader.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Default connection:
|
||||||
|
- Server: `localhost`
|
||||||
|
- Username: `sysadmin`
|
||||||
|
- Password: `masterkey`
|
||||||
|
- Output: `geviScope_config.json`
|
||||||
|
|
||||||
|
### Custom Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GeViScopeConfigReader.exe <hostname> <username> <password> <output-file>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
GeViScopeConfigReader.exe 192.168.1.100 admin mypassword my_config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
The tool exports configuration to JSON in a hierarchical structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"System": {
|
||||||
|
"MediaChannels": {
|
||||||
|
"0000": {
|
||||||
|
"Name": "Camera 1",
|
||||||
|
"Enabled": true,
|
||||||
|
"GlobalNumber": 1,
|
||||||
|
"VideoFormat": "H.264"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Users": {
|
||||||
|
"SysAdmin": {
|
||||||
|
"Name": "System Administrator",
|
||||||
|
"Enabled": true,
|
||||||
|
"Password": "abe6db4c9f5484fae8d79f2e868a673c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
Or open in Visual Studio and build.
|
||||||
|
|
||||||
|
## What This Solves
|
||||||
|
|
||||||
|
**Problem**: The `.set` configuration files are in a proprietary binary format that's difficult to parse.
|
||||||
|
|
||||||
|
**Solution**: Use the GeViScope SDK to read configuration directly from the server in a structured format, then export to JSON.
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- No reverse-engineering needed
|
||||||
|
- Official supported API
|
||||||
|
- Human-readable output
|
||||||
|
- Easy to modify and use programmatically
|
||||||
|
|
||||||
|
## Example: Reading User Information
|
||||||
|
|
||||||
|
The exported JSON makes it easy to access configuration:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var config = JObject.Parse(File.ReadAllText("geviScope_config.json"));
|
||||||
|
|
||||||
|
// Get all users
|
||||||
|
var users = config["System"]["Users"];
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"User: {user["Name"]}");
|
||||||
|
Console.WriteLine($"Enabled: {user["Enabled"]}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modifying Configuration
|
||||||
|
|
||||||
|
To write configuration back to the server:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. Read current config
|
||||||
|
GscRegistry registry = server.CreateRegistry();
|
||||||
|
registry.ReadNodes(...);
|
||||||
|
|
||||||
|
// 2. Find node to modify
|
||||||
|
GscRegNode userNode = registry.FindNode("/System/Users/MyUser");
|
||||||
|
|
||||||
|
// 3. Modify values
|
||||||
|
userNode.WriteBoolean("Enabled", false);
|
||||||
|
userNode.WriteWideString("Name", "New Name");
|
||||||
|
|
||||||
|
// 4. Write back to server
|
||||||
|
GscRegistryWriteRequest[] writeRequests = new GscRegistryWriteRequest[1];
|
||||||
|
writeRequests[0] = new GscRegistryWriteRequest("/System/Users/MyUser", 0);
|
||||||
|
registry.WriteNodes(writeRequests, true); // true = save permanently
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
See the GeViScope SDK documentation for detailed API reference:
|
||||||
|
- `C:\Program Files (x86)\GeViScopeSDK\Documentation\`
|
||||||
|
- Or: `C:\DEV\COPILOT\SOURCES\GeViScope_SDK_text\`
|
||||||
|
|
||||||
|
Key classes:
|
||||||
|
- `GscServer` - Server connection
|
||||||
|
- `GscRegistry` - Configuration registry
|
||||||
|
- `GscRegNode` - Individual configuration node
|
||||||
|
- `GscRegVariant` - Configuration value
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Failed to connect to server"
|
||||||
|
|
||||||
|
- Verify GeViScope server is running
|
||||||
|
- Check hostname/IP address
|
||||||
|
- Verify username and password
|
||||||
|
- Ensure firewall allows connection
|
||||||
|
|
||||||
|
### "Failed to create registry accessor"
|
||||||
|
|
||||||
|
- Server may not support registry API
|
||||||
|
- Try updating GeViScope server to latest version
|
||||||
|
|
||||||
|
### DLL not found errors
|
||||||
|
|
||||||
|
- Ensure GeViScope SDK is installed
|
||||||
|
- Check that DLL paths in .csproj are correct
|
||||||
|
- SDK should be at: `C:\Program Files (x86)\GeViScopeSDK\`
|
||||||
|
|
||||||
|
## Related Tools
|
||||||
|
|
||||||
|
- **GeViSetConfigWriter** (coming soon) - Write configuration to server
|
||||||
|
- **GeViSoftDBReader** (coming soon) - Read GeViSoft database directly
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This tool uses the Geutebruck GeViScope SDK. Refer to your GeViScope license agreement.
|
||||||
192
GeViScopeConfigReader/START_HERE.md
Normal file
192
GeViScopeConfigReader/START_HERE.md
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
# START HERE - GeViScope Configuration Reader
|
||||||
|
|
||||||
|
This tool reads GeViScope server configuration and exports it to human-readable JSON format.
|
||||||
|
|
||||||
|
## ⚠️ Prerequisites Required
|
||||||
|
|
||||||
|
You need to install build tools before you can use this application.
|
||||||
|
|
||||||
|
### What to Install
|
||||||
|
|
||||||
|
1. **.NET SDK 8.0** (provides `dotnet` command)
|
||||||
|
2. **.NET Framework 4.8 Developer Pack** (provides targeting libraries)
|
||||||
|
|
||||||
|
### How to Install
|
||||||
|
|
||||||
|
#### Quick Links (Download both):
|
||||||
|
|
||||||
|
**Download 1:** .NET SDK 8.0
|
||||||
|
https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/sdk-8.0.404-windows-x64-installer
|
||||||
|
|
||||||
|
**Download 2:** .NET Framework 4.8 Developer Pack
|
||||||
|
https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/net48-developer-pack-offline-installer
|
||||||
|
|
||||||
|
#### Installation Steps:
|
||||||
|
|
||||||
|
1. Run both installers (in any order)
|
||||||
|
2. Click through the installation wizards
|
||||||
|
3. **Close and reopen** your terminal after installation
|
||||||
|
4. Verify installation: `dotnet --version` (should show 8.0.xxx)
|
||||||
|
|
||||||
|
**Total time:** About 5-10 minutes
|
||||||
|
|
||||||
|
**Full instructions:** See `C:\DEV\COPILOT\DOTNET_INSTALLATION_GUIDE.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔨 How to Build
|
||||||
|
|
||||||
|
After installing the prerequisites above:
|
||||||
|
|
||||||
|
### Option 1: Use the build script
|
||||||
|
```cmd
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader
|
||||||
|
build.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Build manually
|
||||||
|
```cmd
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader
|
||||||
|
dotnet restore
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output location:** `bin\Debug\net48\GeViScopeConfigReader.exe`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ▶️ How to Run
|
||||||
|
|
||||||
|
### Step 1: Start GeViScope Server
|
||||||
|
|
||||||
|
```cmd
|
||||||
|
cd "C:\Program Files (x86)\GeViScopeSDK\BIN"
|
||||||
|
GSCServer.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Leave this running in a separate window.
|
||||||
|
|
||||||
|
### Step 2: Run the Configuration Reader
|
||||||
|
|
||||||
|
#### Option 1: Use the run script
|
||||||
|
```cmd
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader
|
||||||
|
run.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 2: Run manually
|
||||||
|
```cmd
|
||||||
|
cd C:\DEV\COPILOT\geutebruck-api\GeViScopeConfigReader\bin\Debug\net48
|
||||||
|
GeViScopeConfigReader.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option 3: Run with custom parameters
|
||||||
|
```cmd
|
||||||
|
GeViScopeConfigReader.exe <server> <username> <password> <output.json>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```cmd
|
||||||
|
GeViScopeConfigReader.exe 192.168.1.100 admin mypassword config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Output
|
||||||
|
|
||||||
|
The tool creates a JSON file (default: `geviScope_config.json`) with the complete server configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"System": {
|
||||||
|
"MediaChannels": {
|
||||||
|
"0000": {
|
||||||
|
"Name": "Camera 1",
|
||||||
|
"Enabled": true,
|
||||||
|
"GlobalNumber": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Users": {
|
||||||
|
"SysAdmin": {
|
||||||
|
"Name": "System Administrator",
|
||||||
|
"Password": "abe6db4c9f5484fae8d79f2e868a673c",
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can open this file in any text editor or process it programmatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- **QUICK_START.md** - Step-by-step tutorial with examples
|
||||||
|
- **README.md** - Detailed documentation and API reference
|
||||||
|
- **Program.cs** - Source code with comments
|
||||||
|
- **C:\DEV\COPILOT\DOTNET_INSTALLATION_GUIDE.md** - Full installation guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Quick Checklist
|
||||||
|
|
||||||
|
- [ ] Install .NET SDK 8.0
|
||||||
|
- [ ] Install .NET Framework 4.8 Developer Pack
|
||||||
|
- [ ] Close and reopen terminal
|
||||||
|
- [ ] Run `build.bat` or `dotnet build`
|
||||||
|
- [ ] Start GeViScope Server (GSCServer.exe)
|
||||||
|
- [ ] Run `run.bat` or `GeViScopeConfigReader.exe`
|
||||||
|
- [ ] View the output JSON file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Why This Approach?
|
||||||
|
|
||||||
|
Instead of parsing binary .set files (which is complex and fragile), this tool:
|
||||||
|
|
||||||
|
✓ Uses the **official GeViScope SDK**
|
||||||
|
✓ Connects directly to the **running server**
|
||||||
|
✓ Reads configuration in **documented format**
|
||||||
|
✓ Exports to **human-readable JSON**
|
||||||
|
✓ Works with **any GeViScope version**
|
||||||
|
|
||||||
|
**Result:** Clean, reliable, maintainable configuration access!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ Troubleshooting
|
||||||
|
|
||||||
|
### "dotnet: command not found"
|
||||||
|
- Install .NET SDK 8.0 (see links above)
|
||||||
|
- Close and reopen your terminal
|
||||||
|
|
||||||
|
### "Could not find SDK for TargetFramework"
|
||||||
|
- Install .NET Framework 4.8 Developer Pack (see links above)
|
||||||
|
|
||||||
|
### "Failed to connect to server"
|
||||||
|
- Start GeViScope Server: `C:\Program Files (x86)\GeViScopeSDK\BIN\GSCServer.exe`
|
||||||
|
- Check server hostname/IP
|
||||||
|
- Verify username and password
|
||||||
|
|
||||||
|
### DLL not found errors
|
||||||
|
- Ensure GeViScope SDK is installed
|
||||||
|
- Check paths in GeViScopeConfigReader.csproj
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Next Steps
|
||||||
|
|
||||||
|
Once you have the configuration exported:
|
||||||
|
|
||||||
|
1. **Examine the JSON** - Understand your server configuration
|
||||||
|
2. **Backup configurations** - Export before making changes
|
||||||
|
3. **Compare configurations** - Diff between servers or versions
|
||||||
|
4. **Automate management** - Build tools to modify configuration programmatically
|
||||||
|
|
||||||
|
See QUICK_START.md and README.md for code examples!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Ready to start?** Install the prerequisites above, then run `build.bat`!
|
||||||
40
GeViScopeConfigReader/build.bat
Normal file
40
GeViScopeConfigReader/build.bat
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@echo off
|
||||||
|
REM Build GeViScopeConfigReader
|
||||||
|
|
||||||
|
echo =============================================
|
||||||
|
echo Building GeViScopeConfigReader
|
||||||
|
echo =============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo Restoring NuGet packages...
|
||||||
|
dotnet restore
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Failed to restore packages
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Building project...
|
||||||
|
dotnet build --configuration Debug
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo ERROR: Build failed
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo =============================================
|
||||||
|
echo Build Successful!
|
||||||
|
echo =============================================
|
||||||
|
echo.
|
||||||
|
echo Output location:
|
||||||
|
echo %~dp0bin\Debug\net48\GeViScopeConfigReader.exe
|
||||||
|
echo.
|
||||||
|
echo To run the application:
|
||||||
|
echo 1. Start GeViScope Server: "C:\Program Files (x86)\GeViScopeSDK\BIN\GSCServer.exe"
|
||||||
|
echo 2. Run: %~dp0bin\Debug\net48\GeViScopeConfigReader.exe
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
BIN
GeViScopeConfigReader/newtonsoft.zip
Normal file
BIN
GeViScopeConfigReader/newtonsoft.zip
Normal file
Binary file not shown.
4
GeViScopeConfigReader/packages.config
Normal file
4
GeViScopeConfigReader/packages.config
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||||
|
</packages>
|
||||||
53
GeViScopeConfigReader/run.bat
Normal file
53
GeViScopeConfigReader/run.bat
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
@echo off
|
||||||
|
REM Run GeViScopeConfigReader
|
||||||
|
|
||||||
|
echo =============================================
|
||||||
|
echo GeViScope Configuration Reader
|
||||||
|
echo =============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
cd /d "%~dp0bin\Debug\net48"
|
||||||
|
|
||||||
|
if not exist "GeViScopeConfigReader.exe" (
|
||||||
|
echo ERROR: GeViScopeConfigReader.exe not found
|
||||||
|
echo.
|
||||||
|
echo Please build the project first:
|
||||||
|
echo cd "%~dp0"
|
||||||
|
echo dotnet build
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Make sure GeViScope Server is running!
|
||||||
|
echo If not started: "C:\Program Files (x86)\GeViScopeSDK\BIN\GSCServer.exe"
|
||||||
|
echo.
|
||||||
|
echo Starting configuration reader...
|
||||||
|
echo.
|
||||||
|
echo Default connection:
|
||||||
|
echo Server: localhost
|
||||||
|
echo Username: sysadmin
|
||||||
|
echo Password: masterkey
|
||||||
|
echo Output: geviScope_config.json
|
||||||
|
echo.
|
||||||
|
|
||||||
|
GeViScopeConfigReader.exe
|
||||||
|
|
||||||
|
echo.
|
||||||
|
if exist "geviScope_config.json" (
|
||||||
|
echo =============================================
|
||||||
|
echo Success! Configuration exported to:
|
||||||
|
echo %cd%\geviScope_config.json
|
||||||
|
echo =============================================
|
||||||
|
echo.
|
||||||
|
echo View the file:
|
||||||
|
echo notepad geviScope_config.json
|
||||||
|
echo.
|
||||||
|
) else (
|
||||||
|
echo =============================================
|
||||||
|
echo Export failed - check error messages above
|
||||||
|
echo =============================================
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
12
GeViSoftConfigReader/App.config
Normal file
12
GeViSoftConfigReader/App.config
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||||
|
</startup>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<probing privatePath="." />
|
||||||
|
</assemblyBinding>
|
||||||
|
<loadFromRemoteSources enabled="true"/>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
65
GeViSoftConfigReader/GeViSoftConfigReader.csproj
Normal file
65
GeViSoftConfigReader/GeViSoftConfigReader.csproj
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{C9B6E0D3-9D5F-4E2B-8F7C-6A4D9B2E1F5A}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>GeViSoftConfigReader</RootNamespace>
|
||||||
|
<AssemblyName>GeViSoftConfigReader</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>C:\GEVISOFT\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="GeViProcAPINET_4_0, Version=1.0.0.0, Culture=neutral, processorArchitecture=x86">
|
||||||
|
<SpecificVersion>False</SpecificVersion>
|
||||||
|
<HintPath>C:\GEVISOFT\GeViProcAPINET_4_0.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
|
||||||
|
<HintPath>packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="MainForm.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup> <ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
<None Include="packages.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
193
GeViSoftConfigReader/MainForm.cs
Normal file
193
GeViSoftConfigReader/MainForm.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper;
|
||||||
|
using GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper.ActionDispatcher;
|
||||||
|
using GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper.SystemActions;
|
||||||
|
using GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper.DataBaseQueries;
|
||||||
|
using GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper.DataBaseAnswers;
|
||||||
|
|
||||||
|
namespace GeViSoftConfigReader
|
||||||
|
{
|
||||||
|
public class MainForm : Form
|
||||||
|
{
|
||||||
|
private GeViDatabase database;
|
||||||
|
private string[] args;
|
||||||
|
|
||||||
|
public MainForm(string[] arguments)
|
||||||
|
{
|
||||||
|
this.args = arguments;
|
||||||
|
this.Shown += MainForm_Shown;
|
||||||
|
this.WindowState = FormWindowState.Minimized;
|
||||||
|
this.ShowInTaskbar = false;
|
||||||
|
this.Size = new System.Drawing.Size(1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_Shown(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
this.Hide();
|
||||||
|
|
||||||
|
// Global exception handler - catch EVERYTHING
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\SHOWN_EVENT_FIRED.txt", "Event fired at " + DateTime.Now);
|
||||||
|
RunExport();
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\RUNEXPORT_COMPLETED.txt", "RunExport completed at " + DateTime.Now);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\GLOBAL_EXCEPTION.txt",
|
||||||
|
"GLOBAL EXCEPTION at " + DateTime.Now + Environment.NewLine +
|
||||||
|
"Message: " + ex.Message + Environment.NewLine +
|
||||||
|
"Type: " + ex.GetType().FullName + Environment.NewLine +
|
||||||
|
"Stack: " + ex.StackTrace + Environment.NewLine +
|
||||||
|
"ToString: " + ex.ToString());
|
||||||
|
}
|
||||||
|
catch { /* Ultimate fallback - can't even write error */ }
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Give file operations time to complete before exiting
|
||||||
|
System.Threading.Thread.Sleep(2000);
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunExport()
|
||||||
|
{
|
||||||
|
string logFile = @"C:\GEVISOFT\GeViSoftConfigReader.log";
|
||||||
|
void Log(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string logLine = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + ": " + message + Environment.NewLine;
|
||||||
|
File.AppendAllText(logFile, logLine);
|
||||||
|
// Also force flush by reopening
|
||||||
|
System.Threading.Thread.Sleep(10);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\log_error.txt",
|
||||||
|
"Log Error at " + DateTime.Now + Environment.NewLine + ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Write immediate confirmation that we entered this method
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\runexport_started.txt",
|
||||||
|
"RunExport started at " + DateTime.Now + Environment.NewLine +
|
||||||
|
"Args count: " + (args != null ? args.Length.ToString() : "null"));
|
||||||
|
System.Threading.Thread.Sleep(50); // Ensure file write completes
|
||||||
|
|
||||||
|
// Delete old log if exists
|
||||||
|
if (File.Exists(logFile))
|
||||||
|
{
|
||||||
|
try { File.Delete(logFile); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("=== GeViSoft Configuration Reader Started ===");
|
||||||
|
Log("Working Directory: " + Directory.GetCurrentDirectory());
|
||||||
|
Log("Application Path: " + Application.ExecutablePath);
|
||||||
|
|
||||||
|
// Parse arguments with defaults
|
||||||
|
string hostname = "localhost";
|
||||||
|
string username = "sysadmin";
|
||||||
|
string password = "masterkey";
|
||||||
|
string outputFile = @"C:\GEVISOFT\geviSoft_config.json"; // Explicit full path
|
||||||
|
|
||||||
|
if (args != null && args.Length >= 1) hostname = args[0];
|
||||||
|
if (args != null && args.Length >= 2) username = args[1];
|
||||||
|
if (args != null && args.Length >= 3) password = args[2];
|
||||||
|
if (args != null && args.Length >= 4) outputFile = args[3];
|
||||||
|
|
||||||
|
// Ensure output file has full path
|
||||||
|
if (!Path.IsPathRooted(outputFile))
|
||||||
|
{
|
||||||
|
outputFile = Path.Combine(@"C:\GEVISOFT", outputFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log($"Server: {hostname}, User: {username}, Output: {outputFile}");
|
||||||
|
Log("Creating database connection...");
|
||||||
|
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\before_gevidatabase.txt",
|
||||||
|
"About to create GeViDatabase at " + DateTime.Now);
|
||||||
|
|
||||||
|
database = new GeViDatabase();
|
||||||
|
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\after_gevidatabase.txt",
|
||||||
|
"GeViDatabase created at " + DateTime.Now);
|
||||||
|
|
||||||
|
Log("GeViDatabase object created successfully");
|
||||||
|
Log($"Calling Create({hostname}, {username}, ***)");
|
||||||
|
|
||||||
|
database.Create(hostname, username, password);
|
||||||
|
Log("Create() completed, calling RegisterCallback()");
|
||||||
|
|
||||||
|
database.RegisterCallback();
|
||||||
|
Log("RegisterCallback() completed, calling Connect()");
|
||||||
|
|
||||||
|
GeViConnectResult result = database.Connect();
|
||||||
|
Log($"Connect() returned: {result}");
|
||||||
|
|
||||||
|
if (result != GeViConnectResult.connectOk)
|
||||||
|
{
|
||||||
|
Log($"ERROR: Connection failed: {result}");
|
||||||
|
File.WriteAllText(@"C:\GEVISOFT\connection_failed.txt",
|
||||||
|
"Connection failed: " + result.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("Connected successfully!");
|
||||||
|
|
||||||
|
JObject config = new JObject();
|
||||||
|
config["ServerInfo"] = new JObject
|
||||||
|
{
|
||||||
|
["Hostname"] = hostname,
|
||||||
|
["Connected"] = true,
|
||||||
|
["ConnectionResult"] = result.ToString(),
|
||||||
|
["Time"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
};
|
||||||
|
|
||||||
|
Log("Saving configuration to: " + outputFile);
|
||||||
|
string jsonContent = config.ToString(Formatting.Indented);
|
||||||
|
File.WriteAllText(outputFile, jsonContent);
|
||||||
|
Log($"File written successfully. Size: {new FileInfo(outputFile).Length} bytes");
|
||||||
|
|
||||||
|
Log($"SUCCESS! Config saved to: {outputFile}");
|
||||||
|
|
||||||
|
database.Disconnect();
|
||||||
|
database.Dispose();
|
||||||
|
Log("Database disconnected and disposed");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
string errorLog = @"C:\GEVISOFT\RUNEXPORT_ERROR.txt";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string errorInfo = "=== RUNEXPORT EXCEPTION ===" + Environment.NewLine +
|
||||||
|
"Time: " + DateTime.Now + Environment.NewLine +
|
||||||
|
"Message: " + ex.Message + Environment.NewLine +
|
||||||
|
"Type: " + ex.GetType().FullName + Environment.NewLine +
|
||||||
|
"Stack Trace:" + Environment.NewLine + ex.StackTrace + Environment.NewLine +
|
||||||
|
Environment.NewLine + "Full ToString:" + Environment.NewLine + ex.ToString();
|
||||||
|
|
||||||
|
File.WriteAllText(errorLog, errorInfo);
|
||||||
|
|
||||||
|
// Try to log it too
|
||||||
|
try { Log("EXCEPTION: " + ex.Message); } catch { }
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Last resort - write minimal error
|
||||||
|
try { File.WriteAllText(@"C:\GEVISOFT\ERROR_WRITING_ERROR.txt", "Failed to write error log"); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
GeViSoftConfigReader/Program.cs
Normal file
16
GeViSoftConfigReader/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace GeViSoftConfigReader
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new MainForm(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
GeViSoftConfigReader/Properties/AssemblyInfo.cs
Normal file
18
GeViSoftConfigReader/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle("GeViSoftConfigReader")]
|
||||||
|
[assembly: AssemblyDescription("GeViSoft Configuration Reader and JSON Exporter")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("GeViSoftConfigReader")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
[assembly: Guid("c9b6e0d3-9d5f-4e2b-8f7c-6a4d9b2e1f5a")]
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
4
GeViSoftConfigReader/packages.config
Normal file
4
GeViSoftConfigReader/packages.config
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||||
|
</packages>
|
||||||
311
SDK_INTEGRATION_LESSONS.md
Normal file
311
SDK_INTEGRATION_LESSONS.md
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
# GeViSoft SDK Integration - Critical Lessons Learned
|
||||||
|
|
||||||
|
**Date**: 2025-12-08
|
||||||
|
**Source**: GeViSoftConfigReader development session
|
||||||
|
**Applies to**: geutebruck-api (001-surveillance-api)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Critical Requirements
|
||||||
|
|
||||||
|
### 1. **Full GeViSoft Installation Required**
|
||||||
|
|
||||||
|
❌ **Installing only SDK is NOT sufficient**
|
||||||
|
✅ **Must install GeViSoft FULL application first, then SDK**
|
||||||
|
|
||||||
|
**Why**: The SDK libraries depend on runtime components from the full GeViSoft installation.
|
||||||
|
|
||||||
|
### 2. **Visual C++ 2010 Redistributable (x86) REQUIRED**
|
||||||
|
|
||||||
|
**Critical Dependency**: `vcredist_x86_2010.exe`
|
||||||
|
|
||||||
|
**Error without it**:
|
||||||
|
```
|
||||||
|
FileNotFoundException: Could not load file or assembly 'GeViProcAPINET_4_0.dll'
|
||||||
|
or one of its dependencies. The specified module could not be found.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Installation**:
|
||||||
|
```powershell
|
||||||
|
# Download and install
|
||||||
|
Invoke-WebRequest -Uri 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe' -OutFile 'vcredist_x86_2010.exe'
|
||||||
|
Start-Process -FilePath 'vcredist_x86_2010.exe' -ArgumentList '/install', '/quiet', '/norestart' -Wait
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation Reference**: GeViScope_SDK.txt lines 689-697
|
||||||
|
> "For applications using the .NET-Framework 2.0 the Visual C++ 2008 Redistributable Package...
|
||||||
|
> need to install the Visual C++ 2010 Redistributable Package."
|
||||||
|
|
||||||
|
### 3. **Platform Requirements**
|
||||||
|
|
||||||
|
- **Architecture**: x86 (32-bit) REQUIRED
|
||||||
|
- **.NET Framework**: 4.0+ (tested with 4.8)
|
||||||
|
- **Windows**: Windows 10/11 or Windows Server 2016+
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 SDK Architecture
|
||||||
|
|
||||||
|
### DLL Dependencies
|
||||||
|
|
||||||
|
**GeViProcAPINET_4_0.dll** (Managed .NET wrapper) requires:
|
||||||
|
- `GeViProcAPI.dll` (Native C++ core)
|
||||||
|
- `GscDBI.dll` (Database interface)
|
||||||
|
- `GscActions.dll` (Action system)
|
||||||
|
|
||||||
|
**All DLLs must be in application output directory**: `C:\GEVISOFT\`
|
||||||
|
|
||||||
|
### Connection Workflow
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. Create database object
|
||||||
|
var database = new GeViDatabase();
|
||||||
|
|
||||||
|
// 2. Initialize connection
|
||||||
|
database.Create(hostname, username, password);
|
||||||
|
|
||||||
|
// 3. Register callbacks BEFORE connecting
|
||||||
|
database.RegisterCallback();
|
||||||
|
|
||||||
|
// 4. Connect
|
||||||
|
GeViConnectResult result = database.Connect();
|
||||||
|
|
||||||
|
// 5. Check result
|
||||||
|
if (result != GeViConnectResult.connectOk) {
|
||||||
|
// Handle connection failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Perform operations
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// 7. Cleanup
|
||||||
|
database.Disconnect();
|
||||||
|
database.Dispose();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Order matters!** `RegisterCallback()` must be called BEFORE `Connect()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 GeViServer
|
||||||
|
|
||||||
|
### Server Must Be Running
|
||||||
|
|
||||||
|
**Start server**:
|
||||||
|
```cmd
|
||||||
|
cd C:\GEVISOFT
|
||||||
|
GeViServer.exe console
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via batch file:
|
||||||
|
```cmd
|
||||||
|
startserver.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
### Network Ports
|
||||||
|
|
||||||
|
GeViServer listens on:
|
||||||
|
- **7700, 7701, 7703** (TCP) - API communication
|
||||||
|
- **7777, 7800, 7801, 7803** (TCP) - Additional services
|
||||||
|
- **7704** (UDP)
|
||||||
|
|
||||||
|
**NOT on port 7707** (common misconception)
|
||||||
|
|
||||||
|
### Connection String
|
||||||
|
|
||||||
|
Default connection:
|
||||||
|
- **Hostname**: `localhost`
|
||||||
|
- **Username**: `sysadmin`
|
||||||
|
- **Password**: `masterkey` (default, should be changed)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Query Patterns
|
||||||
|
|
||||||
|
### State Queries (Current Configuration)
|
||||||
|
|
||||||
|
**Pattern**: GetFirst → GetNext iteration
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Example: Enumerate all video inputs (cameras)
|
||||||
|
var query = new CSQGetFirstVideoInput(true, true);
|
||||||
|
var answer = database.SendStateQuery(query);
|
||||||
|
|
||||||
|
while (answer.AnswerKind != AnswerKind.Nothing) {
|
||||||
|
var videoInput = (CSAVideoInputInfo)answer;
|
||||||
|
|
||||||
|
// Process videoInput
|
||||||
|
// - videoInput.GlobalID
|
||||||
|
// - videoInput.Name
|
||||||
|
// - videoInput.Description
|
||||||
|
// - videoInput.HasPTZHead
|
||||||
|
// - videoInput.HasVideoSensor
|
||||||
|
|
||||||
|
// Get next
|
||||||
|
query = new CSQGetNextVideoInput(true, true, videoInput.GlobalID);
|
||||||
|
answer = database.SendStateQuery(query);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Queryable Entities**:
|
||||||
|
- Video Inputs (cameras)
|
||||||
|
- Video Outputs (monitors)
|
||||||
|
- Digital Contacts (I/O)
|
||||||
|
|
||||||
|
### Database Queries (Historical Data)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Create query session
|
||||||
|
var createQuery = new CDBQCreateActionQuery(0);
|
||||||
|
var createAnswer = database.SendDatabaseQuery(createQuery);
|
||||||
|
var handle = (CDBAQueryHandle)createAnswer;
|
||||||
|
|
||||||
|
// Get records
|
||||||
|
var getQuery = new CDBQGetLast(handle.Handle);
|
||||||
|
var getAnswer = database.SendDatabaseQuery(getQuery);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Available**:
|
||||||
|
- Action logs
|
||||||
|
- Alarm logs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Common Pitfalls
|
||||||
|
|
||||||
|
### 1. **Console Apps vs Windows Forms**
|
||||||
|
|
||||||
|
❌ **Console applications** (OutputType=Exe) fail to load mixed-mode C++/CLI DLLs
|
||||||
|
✅ **Windows Forms applications** (OutputType=WinExe) load successfully
|
||||||
|
|
||||||
|
**Workaround**: Use hidden Windows Form:
|
||||||
|
```csharp
|
||||||
|
public class MainForm : Form {
|
||||||
|
public MainForm() {
|
||||||
|
this.WindowState = FormWindowState.Minimized;
|
||||||
|
this.ShowInTaskbar = false;
|
||||||
|
this.Size = new Size(1, 1);
|
||||||
|
this.Shown += MainForm_Shown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_Shown(object sender, EventArgs e) {
|
||||||
|
this.Hide();
|
||||||
|
// Do actual work here
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Output Directory**
|
||||||
|
|
||||||
|
SDK documentation states applications should output to `C:\GEVISOFT\` to ensure DLL dependencies are found.
|
||||||
|
|
||||||
|
### 3. **Application Lifecycle**
|
||||||
|
|
||||||
|
Give file operations time to complete before exit:
|
||||||
|
```csharp
|
||||||
|
finally {
|
||||||
|
System.Threading.Thread.Sleep(2000);
|
||||||
|
Application.Exit();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐍 Python Integration Considerations
|
||||||
|
|
||||||
|
### For Python FastAPI SDK Bridge
|
||||||
|
|
||||||
|
**Challenge**: GeViSoft SDK is .NET/COM, Python needs to interface with it.
|
||||||
|
|
||||||
|
**Options**:
|
||||||
|
|
||||||
|
1. **Subprocess Calls** (Simplest)
|
||||||
|
```python
|
||||||
|
result = subprocess.run([
|
||||||
|
"GeViSoftConfigReader.exe",
|
||||||
|
"localhost", "admin", "password", "output.json"
|
||||||
|
], capture_output=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **pythonnet** (Direct .NET interop)
|
||||||
|
```python
|
||||||
|
import clr
|
||||||
|
clr.AddReference("GeViProcAPINET_4_0")
|
||||||
|
from GEUTEBRUECK.GeViSoftSDKNET.ActionsWrapper import GeViDatabase
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **comtypes** (COM interface)
|
||||||
|
```python
|
||||||
|
from comtypes.client import CreateObject
|
||||||
|
# If SDK exposes COM interface
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **C# Service Bridge** (Recommended for production)
|
||||||
|
- Build C# Windows Service that wraps SDK
|
||||||
|
- Exposes gRPC/REST interface
|
||||||
|
- Python API calls the C# service
|
||||||
|
- Isolates SDK complexity
|
||||||
|
|
||||||
|
### Recommended Approach
|
||||||
|
|
||||||
|
**For geutebruck-api project**:
|
||||||
|
|
||||||
|
1. **Phase 0 Research**: Test all Python integration methods
|
||||||
|
2. **Phase 1**: Implement C# SDK bridge service (like GeViSoftConfigReader but as a service)
|
||||||
|
3. **Phase 2**: Python API communicates with C# bridge via localhost HTTP/gRPC
|
||||||
|
|
||||||
|
**Why**:
|
||||||
|
- SDK stability (crashes don't kill Python API)
|
||||||
|
- Clear separation of concerns
|
||||||
|
- Easier testing (mock the bridge)
|
||||||
|
- Leverage existing GeViSoftConfigReader code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Documentation
|
||||||
|
|
||||||
|
**Extracted PDF Documentation Location**:
|
||||||
|
```
|
||||||
|
C:\DEV\COPILOT\SOURCES\EXTRACTED_TEXT\
|
||||||
|
├── GeViSoft\GeViSoft\GeViSoft_SDK_Documentation.txt
|
||||||
|
└── GeViScope\GeViScope_SDK.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Sections**:
|
||||||
|
- Lines 1298-1616: Database queries and state queries
|
||||||
|
- Lines 689-697: VC++ redistributable requirements
|
||||||
|
- Lines 1822-1824: Application output directory requirements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Working Example
|
||||||
|
|
||||||
|
**GeViSoftConfigReader** (`C:\DEV\COPILOT\geutebruck-api\GeViSoftConfigReader\`)
|
||||||
|
- ✅ Successfully connects to GeViServer
|
||||||
|
- ✅ Queries configuration data
|
||||||
|
- ✅ Exports to JSON
|
||||||
|
- ✅ Proper error handling
|
||||||
|
- ✅ All dependencies resolved
|
||||||
|
|
||||||
|
**Use as reference implementation for API SDK bridge.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Deployment Checklist
|
||||||
|
|
||||||
|
For any application using GeViSoft SDK:
|
||||||
|
|
||||||
|
- [ ] GeViSoft FULL application installed
|
||||||
|
- [ ] GeViSoft SDK installed
|
||||||
|
- [ ] Visual C++ 2010 Redistributable (x86) installed
|
||||||
|
- [ ] Application targets x86 (32-bit)
|
||||||
|
- [ ] Application outputs to `C:\GEVISOFT\` OR all DLLs copied to app directory
|
||||||
|
- [ ] .NET Framework 4.0+ installed
|
||||||
|
- [ ] GeViServer running and accessible
|
||||||
|
- [ ] Correct credentials available
|
||||||
|
- [ ] Windows Forms pattern used (not console app) for .NET applications
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**End of Document**
|
||||||
1396
specs/001-surveillance-api/contracts/openapi.yaml
Normal file
1396
specs/001-surveillance-api/contracts/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
768
specs/001-surveillance-api/data-model.md
Normal file
768
specs/001-surveillance-api/data-model.md
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
# Data Model: Geutebruck Video Surveillance API
|
||||||
|
|
||||||
|
**Branch**: `001-surveillance-api` | **Date**: 2025-12-08
|
||||||
|
**Input**: [spec.md](./spec.md) requirements | [research.md](./research.md) technical decisions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document defines all data entities, their schemas, relationships, validation rules, and state transitions for the Geutebruck Video Surveillance API.
|
||||||
|
|
||||||
|
**Entity Categories**:
|
||||||
|
- **Authentication**: User, Session, Token
|
||||||
|
- **Surveillance**: Camera, Stream, Recording
|
||||||
|
- **Events**: Event, EventSubscription
|
||||||
|
- **Configuration**: AnalyticsConfig, PTZPreset
|
||||||
|
- **Audit**: AuditLog
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Authentication Entities
|
||||||
|
|
||||||
|
### 1.1 User
|
||||||
|
|
||||||
|
Represents an API user with authentication credentials and permissions.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class User(BaseModel):
|
||||||
|
id: UUID = Field(default_factory=uuid4)
|
||||||
|
username: str = Field(min_length=3, max_length=50, pattern="^[a-zA-Z0-9_-]+$")
|
||||||
|
email: EmailStr
|
||||||
|
hashed_password: str # bcrypt hash
|
||||||
|
role: UserRole # viewer, operator, administrator
|
||||||
|
permissions: List[Permission] # Granular camera-level permissions
|
||||||
|
is_active: bool = True
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
last_login: Optional[datetime] = None
|
||||||
|
|
||||||
|
class UserRole(str, Enum):
|
||||||
|
VIEWER = "viewer" # Read-only camera access
|
||||||
|
OPERATOR = "operator" # Camera control + viewing
|
||||||
|
ADMINISTRATOR = "administrator" # Full system configuration
|
||||||
|
|
||||||
|
class Permission(BaseModel):
|
||||||
|
resource_type: str # "camera", "recording", "analytics"
|
||||||
|
resource_id: int # Channel ID or "*" for all
|
||||||
|
actions: List[str] # ["view", "ptz", "record", "configure"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `username`: Unique, alphanumeric with dash/underscore only
|
||||||
|
- `email`: Valid email format, unique
|
||||||
|
- `hashed_password`: Never returned in API responses
|
||||||
|
- `role`: Must be one of defined roles
|
||||||
|
- `permissions`: Empty list defaults to role-based permissions
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
- User → Session (one-to-many): User can have multiple active sessions
|
||||||
|
- User → AuditLog (one-to-many): All user actions logged
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"username": "operator1",
|
||||||
|
"email": "operator1@example.com",
|
||||||
|
"role": "operator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"resource_type": "camera",
|
||||||
|
"resource_id": 1,
|
||||||
|
"actions": ["view", "ptz"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource_type": "camera",
|
||||||
|
"resource_id": 2,
|
||||||
|
"actions": ["view"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"is_active": true,
|
||||||
|
"created_at": "2025-12-01T10:00:00Z",
|
||||||
|
"last_login": "2025-12-08T14:30:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 Session
|
||||||
|
|
||||||
|
Represents an active authentication session with JWT tokens.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class Session(BaseModel):
|
||||||
|
session_id: str = Field(...) # JTI from JWT
|
||||||
|
user_id: UUID
|
||||||
|
access_token_jti: str
|
||||||
|
refresh_token_jti: Optional[str] = None
|
||||||
|
ip_address: str
|
||||||
|
user_agent: str
|
||||||
|
created_at: datetime
|
||||||
|
last_activity: datetime
|
||||||
|
expires_at: datetime
|
||||||
|
|
||||||
|
class TokenPair(BaseModel):
|
||||||
|
access_token: str # JWT token string
|
||||||
|
refresh_token: str
|
||||||
|
token_type: str = "bearer"
|
||||||
|
expires_in: int # Seconds until access token expires
|
||||||
|
```
|
||||||
|
|
||||||
|
**State Transitions**:
|
||||||
|
```
|
||||||
|
Created → Active → Refreshed → Expired/Revoked
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `session_id`: Unique, UUID format
|
||||||
|
- `access_token_jti`: Must match JWT jti claim
|
||||||
|
- `ip_address`: Valid IPv4/IPv6 address
|
||||||
|
- `expires_at`: Auto-set based on JWT expiration
|
||||||
|
|
||||||
|
**Storage**: Redis with TTL matching token expiration
|
||||||
|
|
||||||
|
**Redis Keys**:
|
||||||
|
```
|
||||||
|
session:{user_id}:{session_id} → Session JSON
|
||||||
|
refresh:{user_id}:{refresh_token_jti} → Refresh token metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Surveillance Entities
|
||||||
|
|
||||||
|
### 2.1 Camera
|
||||||
|
|
||||||
|
Represents a video input channel/camera with capabilities and status.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class Camera(BaseModel):
|
||||||
|
id: int # Channel ID from GeViScope
|
||||||
|
global_id: str # GeViScope GlobalID (UUID)
|
||||||
|
name: str = Field(min_length=1, max_length=100)
|
||||||
|
description: Optional[str] = Field(max_length=500)
|
||||||
|
location: Optional[str] = Field(max_length=200)
|
||||||
|
status: CameraStatus
|
||||||
|
capabilities: CameraCapabilities
|
||||||
|
stream_info: Optional[StreamInfo] = None
|
||||||
|
recording_status: RecordingStatus
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class CameraStatus(str, Enum):
|
||||||
|
ONLINE = "online"
|
||||||
|
OFFLINE = "offline"
|
||||||
|
ERROR = "error"
|
||||||
|
MAINTENANCE = "maintenance"
|
||||||
|
|
||||||
|
class CameraCapabilities(BaseModel):
|
||||||
|
has_ptz: bool = False
|
||||||
|
has_video_sensor: bool = False # Motion detection
|
||||||
|
has_contrast_detection: bool = False
|
||||||
|
has_sync_detection: bool = False
|
||||||
|
supported_analytics: List[AnalyticsType] = []
|
||||||
|
supported_resolutions: List[str] = [] # ["1920x1080", "1280x720"]
|
||||||
|
supported_formats: List[str] = [] # ["h264", "mjpeg"]
|
||||||
|
|
||||||
|
class AnalyticsType(str, Enum):
|
||||||
|
VMD = "vmd" # Video Motion Detection
|
||||||
|
NPR = "npr" # Number Plate Recognition
|
||||||
|
OBTRACK = "obtrack" # Object Tracking
|
||||||
|
GTECT = "gtect" # Perimeter Protection
|
||||||
|
CPA = "cpa" # Camera Position Analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
**State Transitions**:
|
||||||
|
```
|
||||||
|
Offline ⟷ Online ⟷ Error
|
||||||
|
↓
|
||||||
|
Maintenance
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `id`: Positive integer, corresponds to GeViScope channel ID
|
||||||
|
- `name`: Required, user-friendly camera identifier
|
||||||
|
- `status`: Updated via SDK events
|
||||||
|
- `capabilities`: Populated from GeViScope VideoInputInfo
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
- Camera → Stream (one-to-many): Multiple concurrent streams per camera
|
||||||
|
- Camera → Recording (one-to-many): Recording segments for this camera
|
||||||
|
- Camera → AnalyticsConfig (one-to-one): Analytics configuration
|
||||||
|
- Camera → PTZPreset (one-to-many): Saved PTZ positions
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"global_id": "a7b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6",
|
||||||
|
"name": "Entrance Camera",
|
||||||
|
"description": "Main entrance monitoring",
|
||||||
|
"location": "Building A - Main Entrance",
|
||||||
|
"status": "online",
|
||||||
|
"capabilities": {
|
||||||
|
"has_ptz": true,
|
||||||
|
"has_video_sensor": true,
|
||||||
|
"has_contrast_detection": true,
|
||||||
|
"has_sync_detection": true,
|
||||||
|
"supported_analytics": ["vmd", "obtrack"],
|
||||||
|
"supported_resolutions": ["1920x1080", "1280x720"],
|
||||||
|
"supported_formats": ["h264", "mjpeg"]
|
||||||
|
},
|
||||||
|
"recording_status": {
|
||||||
|
"is_recording": true,
|
||||||
|
"mode": "continuous",
|
||||||
|
"start_time": "2025-12-08T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 Stream
|
||||||
|
|
||||||
|
Represents an active video stream session.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class Stream(BaseModel):
|
||||||
|
stream_id: UUID = Field(default_factory=uuid4)
|
||||||
|
camera_id: int # Channel ID
|
||||||
|
user_id: UUID
|
||||||
|
stream_url: HttpUrl # Authenticated URL to GeViScope stream
|
||||||
|
format: str # "h264", "mjpeg"
|
||||||
|
resolution: str # "1920x1080"
|
||||||
|
fps: int = Field(ge=1, le=60)
|
||||||
|
quality: int = Field(ge=1, le=100) # Quality percentage
|
||||||
|
started_at: datetime
|
||||||
|
last_activity: datetime
|
||||||
|
expires_at: datetime
|
||||||
|
status: StreamStatus
|
||||||
|
|
||||||
|
class StreamStatus(str, Enum):
|
||||||
|
INITIALIZING = "initializing"
|
||||||
|
ACTIVE = "active"
|
||||||
|
PAUSED = "paused"
|
||||||
|
STOPPED = "stopped"
|
||||||
|
ERROR = "error"
|
||||||
|
|
||||||
|
class StreamRequest(BaseModel):
|
||||||
|
"""Request model for initiating a stream"""
|
||||||
|
format: str = "h264"
|
||||||
|
resolution: Optional[str] = None # Default: camera's max resolution
|
||||||
|
fps: Optional[int] = None # Default: camera's max FPS
|
||||||
|
quality: int = Field(default=90, ge=1, le=100)
|
||||||
|
|
||||||
|
class StreamResponse(BaseModel):
|
||||||
|
"""Response containing stream access details"""
|
||||||
|
stream_id: UUID
|
||||||
|
camera_id: int
|
||||||
|
stream_url: HttpUrl # Token-authenticated URL
|
||||||
|
format: str
|
||||||
|
resolution: str
|
||||||
|
fps: int
|
||||||
|
expires_at: datetime # Stream URL expiration
|
||||||
|
websocket_url: Optional[HttpUrl] = None # For WebSocket-based streams
|
||||||
|
```
|
||||||
|
|
||||||
|
**State Transitions**:
|
||||||
|
```
|
||||||
|
Initializing → Active ⟷ Paused → Stopped
|
||||||
|
↓
|
||||||
|
Error
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `camera_id`: Must reference existing, online camera
|
||||||
|
- `stream_url`: Contains time-limited JWT token
|
||||||
|
- `expires_at`: Default 1 hour from creation
|
||||||
|
- `format`, `resolution`, `fps`: Must be supported by camera
|
||||||
|
|
||||||
|
**Lifecycle**:
|
||||||
|
1. Client requests stream: `POST /cameras/{id}/stream`
|
||||||
|
2. API generates token-authenticated URL
|
||||||
|
3. Client connects directly to GeViScope stream URL
|
||||||
|
4. Stream auto-expires after TTL
|
||||||
|
5. Client can extend via API: `POST /streams/{id}/extend`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.3 Recording
|
||||||
|
|
||||||
|
Represents a video recording segment.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class Recording(BaseModel):
|
||||||
|
id: UUID = Field(default_factory=uuid4)
|
||||||
|
camera_id: int
|
||||||
|
start_time: datetime
|
||||||
|
end_time: Optional[datetime] = None # None if still recording
|
||||||
|
duration_seconds: Optional[int] = None
|
||||||
|
file_size_bytes: Optional[int] = None
|
||||||
|
trigger: RecordingTrigger
|
||||||
|
status: RecordingStatus
|
||||||
|
export_url: Optional[HttpUrl] = None # If exported
|
||||||
|
metadata: RecordingMetadata
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class RecordingTrigger(str, Enum):
|
||||||
|
SCHEDULED = "scheduled" # Time-based schedule
|
||||||
|
EVENT = "event" # Triggered by alarm/analytics
|
||||||
|
MANUAL = "manual" # User-initiated
|
||||||
|
CONTINUOUS = "continuous" # Always recording
|
||||||
|
|
||||||
|
class RecordingStatus(str, Enum):
|
||||||
|
RECORDING = "recording"
|
||||||
|
COMPLETED = "completed"
|
||||||
|
FAILED = "failed"
|
||||||
|
EXPORTING = "exporting"
|
||||||
|
EXPORTED = "exported"
|
||||||
|
|
||||||
|
class RecordingMetadata(BaseModel):
|
||||||
|
event_id: Optional[str] = None # If event-triggered
|
||||||
|
pre_alarm_seconds: int = 0
|
||||||
|
post_alarm_seconds: int = 0
|
||||||
|
tags: List[str] = []
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
class RecordingQueryParams(BaseModel):
|
||||||
|
camera_id: Optional[int] = None
|
||||||
|
start_time: Optional[datetime] = None
|
||||||
|
end_time: Optional[datetime] = None
|
||||||
|
trigger: Optional[RecordingTrigger] = None
|
||||||
|
limit: int = Field(default=50, ge=1, le=1000)
|
||||||
|
offset: int = Field(default=0, ge=0)
|
||||||
|
```
|
||||||
|
|
||||||
|
**State Transitions**:
|
||||||
|
```
|
||||||
|
Recording → Completed
|
||||||
|
↓
|
||||||
|
Exporting → Exported
|
||||||
|
↓
|
||||||
|
Failed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `camera_id`: Must exist
|
||||||
|
- `start_time`: Cannot be in future
|
||||||
|
- `end_time`: Must be after start_time
|
||||||
|
- `file_size_bytes`: Calculated from ring buffer
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
- Recording → Camera (many-to-one)
|
||||||
|
- Recording → Event (many-to-one, optional)
|
||||||
|
|
||||||
|
**Ring Buffer Handling**:
|
||||||
|
- Oldest recordings automatically deleted when buffer full
|
||||||
|
- `retention_policy` determines minimum retention period
|
||||||
|
- API exposes capacity warnings
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Event Entities
|
||||||
|
|
||||||
|
### 3.1 Event
|
||||||
|
|
||||||
|
Represents a surveillance event (alarm, analytics, system).
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class Event(BaseModel):
|
||||||
|
id: UUID = Field(default_factory=uuid4)
|
||||||
|
event_type: EventType
|
||||||
|
camera_id: Optional[int] = None # None for system events
|
||||||
|
timestamp: datetime
|
||||||
|
severity: EventSeverity
|
||||||
|
data: EventData # Type-specific event data
|
||||||
|
foreign_key: Optional[str] = None # External system correlation
|
||||||
|
acknowledged: bool = False
|
||||||
|
acknowledged_by: Optional[UUID] = None
|
||||||
|
acknowledged_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class EventType(str, Enum):
|
||||||
|
# Analytics events
|
||||||
|
MOTION_DETECTED = "motion_detected"
|
||||||
|
OBJECT_TRACKED = "object_tracked"
|
||||||
|
LICENSE_PLATE = "license_plate"
|
||||||
|
PERIMETER_BREACH = "perimeter_breach"
|
||||||
|
CAMERA_TAMPER = "camera_tamper"
|
||||||
|
|
||||||
|
# System events
|
||||||
|
CAMERA_ONLINE = "camera_online"
|
||||||
|
CAMERA_OFFLINE = "camera_offline"
|
||||||
|
RECORDING_STARTED = "recording_started"
|
||||||
|
RECORDING_STOPPED = "recording_stopped"
|
||||||
|
STORAGE_WARNING = "storage_warning"
|
||||||
|
|
||||||
|
# Alarm events
|
||||||
|
ALARM_TRIGGERED = "alarm_triggered"
|
||||||
|
ALARM_CLEARED = "alarm_cleared"
|
||||||
|
|
||||||
|
class EventSeverity(str, Enum):
|
||||||
|
INFO = "info"
|
||||||
|
WARNING = "warning"
|
||||||
|
ERROR = "error"
|
||||||
|
CRITICAL = "critical"
|
||||||
|
|
||||||
|
class EventData(BaseModel):
|
||||||
|
"""Base class for type-specific event data"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MotionDetectedData(EventData):
|
||||||
|
zone: str
|
||||||
|
confidence: float = Field(ge=0.0, le=1.0)
|
||||||
|
snapshot_url: Optional[HttpUrl] = None
|
||||||
|
|
||||||
|
class LicensePlateData(EventData):
|
||||||
|
plate_number: str
|
||||||
|
country_code: str
|
||||||
|
confidence: float = Field(ge=0.0, le=1.0)
|
||||||
|
snapshot_url: Optional[HttpUrl] = None
|
||||||
|
is_watchlist_match: bool = False
|
||||||
|
|
||||||
|
class ObjectTrackedData(EventData):
|
||||||
|
tracking_id: str
|
||||||
|
object_type: str # "person", "vehicle"
|
||||||
|
zone_entered: Optional[str] = None
|
||||||
|
zone_exited: Optional[str] = None
|
||||||
|
dwell_time_seconds: Optional[int] = None
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `event_type`: Must be valid EventType
|
||||||
|
- `camera_id`: Required for camera events, None for system events
|
||||||
|
- `timestamp`: Auto-set to current time if not provided
|
||||||
|
- `severity`: Must match event type severity mapping
|
||||||
|
|
||||||
|
**Relationships**:
|
||||||
|
- Event → Camera (many-to-one, optional)
|
||||||
|
- Event → Recording (one-to-one, optional)
|
||||||
|
- Event → User (acknowledged_by, many-to-one, optional)
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
|
||||||
|
"event_type": "motion_detected",
|
||||||
|
"camera_id": 5,
|
||||||
|
"timestamp": "2025-12-08T14:45:23Z",
|
||||||
|
"severity": "warning",
|
||||||
|
"data": {
|
||||||
|
"zone": "entrance",
|
||||||
|
"confidence": 0.95,
|
||||||
|
"snapshot_url": "https://api.example.com/snapshots/abc123.jpg"
|
||||||
|
},
|
||||||
|
"acknowledged": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3.2 EventSubscription
|
||||||
|
|
||||||
|
Represents a WebSocket client's event subscription.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class EventSubscription(BaseModel):
|
||||||
|
subscription_id: UUID = Field(default_factory=uuid4)
|
||||||
|
user_id: UUID
|
||||||
|
connection_id: str # WebSocket connection identifier
|
||||||
|
filters: EventFilter
|
||||||
|
created_at: datetime
|
||||||
|
last_heartbeat: datetime
|
||||||
|
|
||||||
|
class EventFilter(BaseModel):
|
||||||
|
event_types: Optional[List[EventType]] = None # None = all types
|
||||||
|
camera_ids: Optional[List[int]] = None # None = all cameras
|
||||||
|
severity: Optional[EventSeverity] = None # Minimum severity
|
||||||
|
include_acknowledged: bool = False
|
||||||
|
|
||||||
|
class EventNotification(BaseModel):
|
||||||
|
"""WebSocket message format"""
|
||||||
|
subscription_id: UUID
|
||||||
|
event: Event
|
||||||
|
sequence_number: int # For detecting missed events
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `filters.camera_ids`: User must have view permission for each camera
|
||||||
|
- `last_heartbeat`: Updated every 30 seconds, timeout after 90 seconds
|
||||||
|
|
||||||
|
**Storage**: Redis with 5-minute retention for reconnection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Configuration Entities
|
||||||
|
|
||||||
|
### 4.1 AnalyticsConfig
|
||||||
|
|
||||||
|
Configuration for video analytics on a camera.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class AnalyticsConfig(BaseModel):
|
||||||
|
camera_id: int
|
||||||
|
analytics_type: AnalyticsType
|
||||||
|
enabled: bool = False
|
||||||
|
config: AnalyticsTypeConfig # Type-specific configuration
|
||||||
|
updated_at: datetime
|
||||||
|
updated_by: UUID
|
||||||
|
|
||||||
|
class VMDConfig(AnalyticsTypeConfig):
|
||||||
|
"""Video Motion Detection configuration"""
|
||||||
|
zones: List[DetectionZone]
|
||||||
|
sensitivity: int = Field(ge=1, le=10, default=5)
|
||||||
|
min_object_size: int = Field(ge=1, le=100, default=10) # Percentage
|
||||||
|
ignore_zones: List[DetectionZone] = []
|
||||||
|
|
||||||
|
class DetectionZone(BaseModel):
|
||||||
|
name: str
|
||||||
|
polygon: List[Point] # List of x,y coordinates
|
||||||
|
|
||||||
|
class Point(BaseModel):
|
||||||
|
x: int = Field(ge=0, le=100) # Percentage of frame width
|
||||||
|
y: int = Field(ge=0, le=100) # Percentage of frame height
|
||||||
|
|
||||||
|
class NPRConfig(AnalyticsTypeConfig):
|
||||||
|
"""Number Plate Recognition configuration"""
|
||||||
|
zones: List[DetectionZone]
|
||||||
|
country_codes: List[str] = ["*"] # ["US", "DE"] or "*" for all
|
||||||
|
min_confidence: float = Field(ge=0.0, le=1.0, default=0.7)
|
||||||
|
watchlist: List[str] = [] # List of plate numbers to alert on
|
||||||
|
|
||||||
|
class OBTRACKConfig(AnalyticsTypeConfig):
|
||||||
|
"""Object Tracking configuration"""
|
||||||
|
object_types: List[str] = ["person", "vehicle"]
|
||||||
|
min_dwell_time: int = Field(ge=0, default=5) # Seconds
|
||||||
|
count_lines: List[CountLine] = []
|
||||||
|
|
||||||
|
class CountLine(BaseModel):
|
||||||
|
name: str
|
||||||
|
point_a: Point
|
||||||
|
point_b: Point
|
||||||
|
direction: str # "in", "out", "both"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `camera_id`: Must exist and support analytics_type
|
||||||
|
- `zones`: At least one zone required when enabled=True
|
||||||
|
- `polygon`: Minimum 3 points, closed polygon
|
||||||
|
- `sensitivity`: Higher = more sensitive
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"camera_id": 5,
|
||||||
|
"analytics_type": "vmd",
|
||||||
|
"enabled": true,
|
||||||
|
"config": {
|
||||||
|
"zones": [
|
||||||
|
{
|
||||||
|
"name": "entrance",
|
||||||
|
"polygon": [
|
||||||
|
{"x": 10, "y": 10},
|
||||||
|
{"x": 90, "y": 10},
|
||||||
|
{"x": 90, "y": 90},
|
||||||
|
{"x": 10, "y": 90}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sensitivity": 7,
|
||||||
|
"min_object_size": 5
|
||||||
|
},
|
||||||
|
"updated_at": "2025-12-08T10:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4.2 PTZPreset
|
||||||
|
|
||||||
|
Saved PTZ camera position.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class PTZPreset(BaseModel):
|
||||||
|
id: int # Preset ID (1-255)
|
||||||
|
camera_id: int
|
||||||
|
name: str = Field(min_length=1, max_length=50)
|
||||||
|
pan: int = Field(ge=-180, le=180) # Degrees
|
||||||
|
tilt: int = Field(ge=-90, le=90) # Degrees
|
||||||
|
zoom: int = Field(ge=0, le=100) # Percentage
|
||||||
|
created_at: datetime
|
||||||
|
created_by: UUID
|
||||||
|
updated_at: datetime
|
||||||
|
|
||||||
|
class PTZCommand(BaseModel):
|
||||||
|
"""PTZ control command"""
|
||||||
|
action: PTZAction
|
||||||
|
speed: Optional[int] = Field(default=50, ge=1, le=100)
|
||||||
|
preset_id: Optional[int] = None # For goto_preset action
|
||||||
|
|
||||||
|
class PTZAction(str, Enum):
|
||||||
|
PAN_LEFT = "pan_left"
|
||||||
|
PAN_RIGHT = "pan_right"
|
||||||
|
TILT_UP = "tilt_up"
|
||||||
|
TILT_DOWN = "tilt_down"
|
||||||
|
ZOOM_IN = "zoom_in"
|
||||||
|
ZOOM_OUT = "zoom_out"
|
||||||
|
STOP = "stop"
|
||||||
|
GOTO_PRESET = "goto_preset"
|
||||||
|
SAVE_PRESET = "save_preset"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `id`: Unique per camera, 1-255 range
|
||||||
|
- `camera_id`: Must exist and have PTZ capability
|
||||||
|
- `name`: Unique per camera
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Audit Entities
|
||||||
|
|
||||||
|
### 5.1 AuditLog
|
||||||
|
|
||||||
|
Audit trail for all privileged operations.
|
||||||
|
|
||||||
|
**Schema**:
|
||||||
|
```python
|
||||||
|
class AuditLog(BaseModel):
|
||||||
|
id: UUID = Field(default_factory=uuid4)
|
||||||
|
timestamp: datetime
|
||||||
|
user_id: UUID
|
||||||
|
username: str
|
||||||
|
action: str # "camera.ptz", "recording.start", "user.create"
|
||||||
|
resource_type: str # "camera", "recording", "user"
|
||||||
|
resource_id: str
|
||||||
|
outcome: AuditOutcome
|
||||||
|
ip_address: str
|
||||||
|
user_agent: str
|
||||||
|
details: Optional[dict] = None # Action-specific metadata
|
||||||
|
|
||||||
|
class AuditOutcome(str, Enum):
|
||||||
|
SUCCESS = "success"
|
||||||
|
FAILURE = "failure"
|
||||||
|
PARTIAL = "partial"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation Rules**:
|
||||||
|
- `timestamp`: Auto-set, immutable
|
||||||
|
- `user_id`: Must exist
|
||||||
|
- `action`: Format "{resource}.{operation}"
|
||||||
|
|
||||||
|
**Storage**: Append-only, never deleted, indexed by user_id and timestamp
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
|
||||||
|
"timestamp": "2025-12-08T14:50:00Z",
|
||||||
|
"user_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"username": "operator1",
|
||||||
|
"action": "camera.ptz",
|
||||||
|
"resource_type": "camera",
|
||||||
|
"resource_id": "5",
|
||||||
|
"outcome": "success",
|
||||||
|
"ip_address": "192.168.1.100",
|
||||||
|
"user_agent": "Mozilla/5.0...",
|
||||||
|
"details": {
|
||||||
|
"ptz_action": "pan_left",
|
||||||
|
"speed": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entity Relationships Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐ ┌─────────────┐ ┌────────────┐
|
||||||
|
│ User │──1:N──│ Session │ │ AuditLog │
|
||||||
|
└────┬────┘ └─────────────┘ └─────┬──────┘
|
||||||
|
│ │
|
||||||
|
│1:N │N:1
|
||||||
|
│ │
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ EventSubscription│ │
|
||||||
|
└─────────────────┘ │
|
||||||
|
│
|
||||||
|
┌────────────┐ ┌─────────┐ ┌──────▼─────┐
|
||||||
|
│ Camera │──1:N──│ Stream │ │ Recording │
|
||||||
|
└─────┬──────┘ └─────────┘ └──────┬─────┘
|
||||||
|
│ │
|
||||||
|
│1:N │N:1
|
||||||
|
▼ │
|
||||||
|
┌─────────────────┐ │
|
||||||
|
│ AnalyticsConfig │ │
|
||||||
|
└─────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│1:N │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────┐ ┌────────────────────────────┐
|
||||||
|
│ PTZPreset │ │ Event │
|
||||||
|
└─────────────┘ └────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Rules Summary
|
||||||
|
|
||||||
|
| Entity | Key Validations |
|
||||||
|
|--------|-----------------|
|
||||||
|
| User | Unique username/email, valid role, bcrypt password |
|
||||||
|
| Session | Valid JWT, IP address, TTL enforced |
|
||||||
|
| Camera | Valid channel ID, status from SDK, capabilities match |
|
||||||
|
| Stream | Camera online, token authentication, supported formats |
|
||||||
|
| Recording | Valid time range, camera exists, ring buffer aware |
|
||||||
|
| Event | Valid type, severity, camera permissions |
|
||||||
|
| EventSubscription | User has camera permissions |
|
||||||
|
| AnalyticsConfig | Camera supports type, valid zones/settings |
|
||||||
|
| PTZPreset | Camera has PTZ, valid coordinates |
|
||||||
|
| AuditLog | Immutable, complete metadata |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State Machine Definitions
|
||||||
|
|
||||||
|
### Camera Status State Machine
|
||||||
|
```
|
||||||
|
[Offline] ──detect_online──▶ [Online] ──detect_offline──▶ [Offline]
|
||||||
|
│ ▲ │
|
||||||
|
│ └───recover_error────────┘
|
||||||
|
│
|
||||||
|
└──detect_error──▶ [Error]
|
||||||
|
│
|
||||||
|
┌─────────────────────┘
|
||||||
|
▼
|
||||||
|
[Maintenance] ──restore──▶ [Online]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recording Status State Machine
|
||||||
|
```
|
||||||
|
[Recording] ──complete──▶ [Completed] ──export──▶ [Exporting] ──finish──▶ [Exported]
|
||||||
|
│ │
|
||||||
|
└──error──▶ [Failed] ◀──────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream Status State Machine
|
||||||
|
```
|
||||||
|
[Initializing] ──ready──▶ [Active] ──pause──▶ [Paused] ──resume──▶ [Active]
|
||||||
|
│ │
|
||||||
|
└──stop──▶ [Stopped] ◀──────────────────┘
|
||||||
|
│
|
||||||
|
└──error──▶ [Error]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Phase 1 Status**: ✅ Data model complete
|
||||||
|
**Next**: Generate OpenAPI contracts
|
||||||
@@ -1,450 +1,403 @@
|
|||||||
# Implementation Plan: Geutebruck Video Surveillance API
|
# Implementation Plan: Geutebruck Surveillance API
|
||||||
|
|
||||||
**Branch**: `001-surveillance-api` | **Date**: 2025-11-13 | **Spec**: [spec.md](./spec.md)
|
**Branch**: `001-surveillance-api` | **Date**: 2025-12-08 | **Spec**: [spec.md](./spec.md)
|
||||||
**Input**: Feature specification from `/specs/001-surveillance-api/spec.md`
|
**Input**: Feature specification from `/specs/001-surveillance-api/spec.md`
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Build a complete RESTful API for Geutebruck GeViScope/GeViSoft video surveillance system control, enabling developers to create custom surveillance applications without direct SDK integration. The API will provide authentication, live video streaming, PTZ camera control, real-time event notifications, recording management, and video analytics configuration through a secure, well-documented REST/WebSocket interface.
|
Build a production-ready REST API for Geutebruck GeViScope/GeViSoft video surveillance systems, enabling developers to integrate surveillance capabilities into custom applications without direct SDK complexity. The system uses a C# gRPC bridge to interface with the GeViScope SDK, exposing clean REST/WebSocket endpoints through Python FastAPI.
|
||||||
|
|
||||||
**Technical Approach**: Python FastAPI service running on Windows, translating REST/WebSocket requests to GeViScope SDK actions through an abstraction layer, with JWT authentication, Redis caching, and auto-generated OpenAPI documentation.
|
**Technical Approach**: Python FastAPI + C# gRPC SDK Bridge + GeViScope SDK → delivers <200ms API responses, supports 100+ concurrent video streams, and handles 1000+ WebSocket event subscribers.
|
||||||
|
|
||||||
## Technical Context
|
## Technical Context
|
||||||
|
|
||||||
**Language/Version**: Python 3.11+
|
**Language/Version**: Python 3.11+, C# .NET Framework 4.8 (SDK bridge), C# .NET 8.0 (gRPC service)
|
||||||
**Primary Dependencies**:
|
**Primary Dependencies**:
|
||||||
- FastAPI 0.104+ (async web framework with auto OpenAPI docs)
|
- **Python**: FastAPI, Uvicorn, SQLAlchemy, Redis (aioredis), protobuf, grpcio, PyJWT, asyncio
|
||||||
- Pydantic 2.5+ (data validation and settings management)
|
- **C#**: GeViScope SDK (GeViProcAPINET_4_0.dll), Grpc.Core, Google.Protobuf
|
||||||
- python-jose 3.3+ (JWT token generation and validation)
|
**Storage**: PostgreSQL 14+ (user management, session storage, audit logs), Redis 6.0+ (session cache, pub/sub for WebSocket events)
|
||||||
- passlib 1.7+ (password hashing with bcrypt)
|
**Testing**: pytest (Python), xUnit (.NET), 80% minimum coverage, TDD enforced
|
||||||
- Redis-py 5.0+ (session storage and caching)
|
**Target Platform**: Windows Server 2016+ (SDK bridge + GeViServer), Linux (FastAPI server - optional)
|
||||||
- python-multipart (file upload support for video exports)
|
**Project Type**: Web (backend API + SDK bridge service)
|
||||||
- uvicorn 0.24+ (ASGI server)
|
|
||||||
- websockets 12.0+ (WebSocket support built into FastAPI)
|
|
||||||
- pywin32 or comtypes (GeViScope SDK COM interface)
|
|
||||||
|
|
||||||
**Storage**:
|
|
||||||
- Redis 7.2+ for session management, API key caching, rate limiting counters
|
|
||||||
- Optional: SQLite for development / PostgreSQL for production audit logs
|
|
||||||
- GeViScope SDK manages video storage (ring buffer architecture)
|
|
||||||
|
|
||||||
**Testing**:
|
|
||||||
- pytest 7.4+ (test framework)
|
|
||||||
- pytest-asyncio (async test support)
|
|
||||||
- httpx (async HTTP client for API testing)
|
|
||||||
- pytest-cov (coverage reporting, target 80%+)
|
|
||||||
- pytest-mock (mocking for SDK bridge testing)
|
|
||||||
|
|
||||||
**Target Platform**: Windows Server 2016+ or Windows 10/11 (required for GeViScope SDK)
|
|
||||||
|
|
||||||
**Project Type**: Single project (API-only service, clients consume REST/WebSocket)
|
|
||||||
|
|
||||||
**Performance Goals**:
|
**Performance Goals**:
|
||||||
- 500 requests/second throughput under normal load
|
- <200ms p95 for metadata queries (camera lists, status)
|
||||||
- < 200ms response time for metadata queries (p95)
|
- <2s stream initialization
|
||||||
- < 500ms for PTZ commands
|
- <100ms event notification delivery
|
||||||
- < 100ms event notification delivery
|
- 100+ concurrent video streams
|
||||||
- Support 100+ concurrent video streams
|
- 1000+ concurrent WebSocket connections
|
||||||
- Support 1000+ concurrent WebSocket connections
|
|
||||||
|
|
||||||
**Constraints**:
|
**Constraints**:
|
||||||
- Must run on Windows (GeViScope SDK requirement)
|
- SDK requires Windows x86 (32-bit) runtime
|
||||||
- Must interface with GeViScope SDK COM/DLL objects
|
- Visual C++ 2010 Redistributable (x86) mandatory
|
||||||
- Channel-based operations (Channel ID parameter required)
|
- Full GeViSoft installation required (not just SDK)
|
||||||
- Video streaming limited by GeViScope SDK license and hardware
|
- GeViServer must be running on network-accessible host
|
||||||
- Ring buffer architecture bounds recording capabilities
|
- All SDK operations must use Channel-based architecture
|
||||||
- TLS 1.2+ required in production
|
|
||||||
|
|
||||||
**Scale/Scope**:
|
**Scale/Scope**:
|
||||||
- 10-100 concurrent operators
|
- Support 50+ cameras per installation
|
||||||
- 50-500 cameras per deployment
|
- Handle 10k+ events/hour during peak activity
|
||||||
- 30 API endpoints across 6 resource types
|
- Store 90 days audit logs (configurable)
|
||||||
- 10 WebSocket event types
|
- Support 100+ concurrent operators
|
||||||
- 8 video analytics types (VMD, NPR, OBTRACK, etc.)
|
|
||||||
|
|
||||||
## Constitution Check
|
## Constitution Check
|
||||||
|
|
||||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||||
|
|
||||||
### ✅ Principle I: Security-First (NON-NEGOTIABLE)
|
### Constitution Alignment
|
||||||
- [x] JWT authentication implemented for all protected endpoints
|
|
||||||
- [x] TLS 1.2+ enforced (configured in deployment, not code)
|
|
||||||
- [x] RBAC with 3 roles (viewer, operator, administrator)
|
|
||||||
- [x] Granular per-camera permissions
|
|
||||||
- [x] Audit logging for privileged operations
|
|
||||||
- [x] Rate limiting on authentication endpoints
|
|
||||||
- [x] No credentials in source code (environment variables)
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - Security requirements addressed in architecture
|
✅ **Single Source of Truth**: OpenAPI spec serves as the contract, auto-generated from code
|
||||||
|
✅ **Test-First Development**: TDD enforced with pytest/xUnit, 80% minimum coverage
|
||||||
|
✅ **Simplicity**: REST over custom protocols, JWT over session cookies, direct stream URLs over proxying
|
||||||
|
✅ **Clear Abstractions**: SDK Bridge isolates SDK complexity from Python API layer
|
||||||
|
✅ **Error Handling**: SDK errors translated to HTTP status codes with user-friendly messages
|
||||||
|
✅ **Documentation**: Auto-generated OpenAPI docs at `/docs`, quickstart guide provided
|
||||||
|
✅ **Security First**: JWT authentication, RBAC, rate limiting, audit logging, TLS enforcement
|
||||||
|
|
||||||
### ✅ Principle II: RESTful API Design
|
### Exceptions to Constitution
|
||||||
- [x] Resources represent surveillance entities (cameras, events, recordings)
|
|
||||||
- [x] Standard HTTP methods (GET, POST, PUT, DELETE)
|
|
||||||
- [x] URL structure `/api/v1/{resource}/{id}/{action}`
|
|
||||||
- [x] JSON data exchange
|
|
||||||
- [x] Proper HTTP status codes
|
|
||||||
- [x] Stateless JWT authentication
|
|
||||||
- [x] API versioning in URL path
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - REST principles followed
|
None. All design decisions align with constitution principles.
|
||||||
|
|
||||||
### ✅ Principle III: Test-Driven Development (NON-NEGOTIABLE)
|
|
||||||
- [x] Tests written before implementation (TDD enforced)
|
|
||||||
- [x] 80% coverage target for SDK bridge layer
|
|
||||||
- [x] Unit, integration, and E2E tests planned
|
|
||||||
- [x] pytest framework selected
|
|
||||||
- [x] CI/CD blocks on test failures
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - TDD workflow defined
|
|
||||||
|
|
||||||
### ✅ Principle IV: SDK Abstraction Layer
|
|
||||||
- [x] SDK Bridge isolates GeViScope SDK from API layer
|
|
||||||
- [x] Translates REST → SDK Actions, SDK Events → WebSocket
|
|
||||||
- [x] Error code translation (Windows → HTTP)
|
|
||||||
- [x] Mockable for testing without hardware
|
|
||||||
- [x] No direct SDK calls from route handlers
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - Abstraction layer designed
|
|
||||||
|
|
||||||
### ✅ Principle V: Performance & Reliability
|
|
||||||
- [x] Performance targets defined and measurable
|
|
||||||
- [x] Retry logic with exponential backoff (3 attempts)
|
|
||||||
- [x] Circuit breaker pattern for SDK communication
|
|
||||||
- [x] Graceful degradation under load (503 vs crash)
|
|
||||||
- [x] Health check endpoint planned
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - Performance and reliability addressed
|
|
||||||
|
|
||||||
### ✅ Technical Constraints Satisfied
|
|
||||||
- [x] Windows platform acknowledged
|
|
||||||
- [x] Python 3.11+ selected
|
|
||||||
- [x] FastAPI framework chosen
|
|
||||||
- [x] Redis for caching
|
|
||||||
- [x] Pytest for testing
|
|
||||||
- [x] SDK integration strategy defined
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - All technical constraints satisfied
|
|
||||||
|
|
||||||
### ✅ Quality Standards Met
|
|
||||||
- [x] 80% test coverage enforced
|
|
||||||
- [x] Code review via PR required
|
|
||||||
- [x] Black formatter + ruff linter
|
|
||||||
- [x] Type hints mandatory (mypy)
|
|
||||||
- [x] OpenAPI auto-generated
|
|
||||||
|
|
||||||
**Status**: ✅ **PASS** - Quality standards defined
|
|
||||||
|
|
||||||
**Overall Gate Status**: ✅ **PASS** - Proceed to Phase 0 Research
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
### Documentation (this feature)
|
### Documentation (this feature)
|
||||||
|
|
||||||
```
|
```text
|
||||||
specs/001-surveillance-api/
|
specs/001-surveillance-api/
|
||||||
├── spec.md # Feature specification (complete)
|
├── plan.md # This file (implementation plan)
|
||||||
├── plan.md # This file (in progress)
|
├── spec.md # Feature specification (user stories, requirements)
|
||||||
├── research.md # Phase 0 output (pending)
|
├── research.md # Phase 0 output (technical research, architectural decisions)
|
||||||
├── data-model.md # Phase 1 output (pending)
|
├── data-model.md # Phase 1 output (entity schemas, relationships, validation)
|
||||||
├── quickstart.md # Phase 1 output (pending)
|
├── quickstart.md # Phase 1 output (developer quick start guide)
|
||||||
├── contracts/ # Phase 1 output (pending)
|
├── contracts/ # Phase 1 output (API contracts)
|
||||||
│ └── openapi.yaml # OpenAPI 3.0 specification
|
│ └── openapi.yaml # Complete OpenAPI 3.0 specification
|
||||||
└── tasks.md # Phase 2 output (via /speckit.tasks)
|
└── tasks.md # Phase 2 output (will be generated by /speckit.tasks)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Source Code (repository root)
|
### Source Code (repository root)
|
||||||
|
|
||||||
```
|
```text
|
||||||
geutebruck-api/
|
geutebruck-api/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── api/
|
│ ├── api/ # Python FastAPI application
|
||||||
│ │ ├── v1/
|
│ │ ├── main.py # FastAPI app entry point
|
||||||
│ │ │ ├── routes/
|
│ │ ├── config.py # Configuration management (env vars)
|
||||||
│ │ │ │ ├── auth.py # Authentication endpoints
|
│ │ ├── models/ # SQLAlchemy ORM models
|
||||||
│ │ │ │ ├── cameras.py # Camera management & streaming
|
│ │ │ ├── user.py
|
||||||
│ │ │ │ ├── events.py # Event subscriptions
|
│ │ │ ├── camera.py
|
||||||
│ │ │ │ ├── recordings.py # Recording management
|
│ │ │ ├── event.py
|
||||||
│ │ │ │ ├── analytics.py # Video analytics config
|
│ │ │ └── audit_log.py
|
||||||
│ │ │ │ └── system.py # Health, status endpoints
|
│ │ ├── schemas/ # Pydantic request/response models
|
||||||
│ │ │ ├── dependencies.py # Route dependencies (auth, etc.)
|
│ │ │ ├── auth.py
|
||||||
│ │ │ ├── schemas.py # Pydantic request/response models
|
│ │ │ ├── camera.py
|
||||||
│ │ │ └── __init__.py
|
│ │ │ ├── stream.py
|
||||||
│ │ ├── middleware/
|
│ │ │ ├── event.py
|
||||||
│ │ │ ├── auth.py # JWT validation middleware
|
│ │ │ └── recording.py
|
||||||
│ │ │ ├── error_handler.py # Global exception handling
|
│ │ ├── routers/ # FastAPI route handlers
|
||||||
│ │ │ ├── rate_limit.py # Rate limiting middleware
|
│ │ │ ├── auth.py # /api/v1/auth/*
|
||||||
│ │ │ └── logging.py # Request/response logging
|
│ │ │ ├── cameras.py # /api/v1/cameras/*
|
||||||
│ │ ├── websocket.py # WebSocket connection manager
|
│ │ │ ├── events.py # /api/v1/events/*
|
||||||
│ │ └── main.py # FastAPI app initialization
|
│ │ │ ├── recordings.py # /api/v1/recordings/*
|
||||||
│ ├── sdk/
|
│ │ │ ├── analytics.py # /api/v1/analytics/*
|
||||||
│ │ ├── bridge.py # Main SDK abstraction interface
|
│ │ │ └── system.py # /api/v1/health, /status
|
||||||
│ │ ├── actions/
|
│ │ ├── services/ # Business logic layer
|
||||||
│ │ │ ├── system.py # SystemActions wrapper
|
│ │ │ ├── auth_service.py
|
||||||
│ │ │ ├── video.py # VideoActions wrapper
|
│ │ │ ├── camera_service.py
|
||||||
│ │ │ ├── camera.py # CameraControlActions wrapper
|
│ │ │ ├── stream_service.py
|
||||||
│ │ │ ├── events.py # Event management wrapper
|
│ │ │ ├── event_service.py
|
||||||
│ │ │ └── analytics.py # Analytics actions wrapper
|
│ │ │ └── recording_service.py
|
||||||
│ │ ├── events/
|
│ │ ├── clients/ # External service clients
|
||||||
│ │ │ ├── dispatcher.py # Event listener and dispatcher
|
│ │ │ ├── sdk_bridge_client.py # gRPC client for SDK bridge
|
||||||
│ │ │ └── translator.py # SDK Event → JSON translator
|
│ │ │ └── redis_client.py # Redis connection pooling
|
||||||
│ │ ├── errors.py # SDK exception types
|
│ │ ├── middleware/ # FastAPI middleware
|
||||||
│ │ └── connection.py # SDK connection management
|
│ │ │ ├── auth_middleware.py
|
||||||
│ ├── services/
|
│ │ │ ├── rate_limiter.py
|
||||||
│ │ ├── auth.py # Authentication service (JWT, passwords)
|
│ │ │ └── error_handler.py
|
||||||
│ │ ├── permissions.py # RBAC and authorization logic
|
│ │ ├── websocket/ # WebSocket event streaming
|
||||||
│ │ ├── camera.py # Camera business logic
|
│ │ │ ├── connection_manager.py
|
||||||
│ │ ├── recording.py # Recording management logic
|
│ │ │ └── event_broadcaster.py
|
||||||
│ │ ├── analytics.py # Analytics configuration logic
|
│ │ ├── utils/ # Utility functions
|
||||||
│ │ └── notifications.py # Event notification service
|
│ │ │ ├── jwt_utils.py
|
||||||
│ ├── models/
|
│ │ │ └── error_translation.py
|
||||||
│ │ ├── user.py # User entity
|
│ │ └── migrations/ # Alembic database migrations
|
||||||
│ │ ├── camera.py # Camera entity
|
│ │ └── versions/
|
||||||
│ │ ├── event.py # Event entity
|
│ │
|
||||||
│ │ ├── recording.py # Recording entity
|
│ └── sdk-bridge/ # C# gRPC service (SDK wrapper)
|
||||||
│ │ └── session.py # Session entity
|
│ ├── GeViScopeBridge.sln
|
||||||
│ ├── database/
|
│ ├── GeViScopeBridge/
|
||||||
│ │ ├── redis.py # Redis connection and helpers
|
│ │ ├── Program.cs # gRPC server entry point
|
||||||
│ │ └── audit.py # Audit log persistence (optional DB)
|
│ │ ├── Services/
|
||||||
│ ├── core/
|
│ │ │ ├── CameraService.cs # Camera operations
|
||||||
│ │ ├── config.py # Settings management (Pydantic Settings)
|
│ │ │ ├── StreamService.cs # Stream management
|
||||||
│ │ ├── security.py # Password hashing, JWT utilities
|
│ │ │ ├── EventService.cs # Event subscriptions
|
||||||
│ │ └── logging.py # Logging configuration
|
│ │ │ ├── RecordingService.cs # Recording management
|
||||||
│ └── utils/
|
│ │ │ └── AnalyticsService.cs # Analytics configuration
|
||||||
│ ├── errors.py # Custom exception classes
|
│ │ ├── SDK/
|
||||||
│ └── validators.py # Custom validation functions
|
│ │ │ ├── GeViDatabaseWrapper.cs
|
||||||
|
│ │ │ ├── StateQueryHandler.cs
|
||||||
|
│ │ │ ├── DatabaseQueryHandler.cs
|
||||||
|
│ │ │ └── ActionDispatcher.cs
|
||||||
|
│ │ ├── Models/ # Internal data models
|
||||||
|
│ │ └── Utils/
|
||||||
|
│ └── Protos/ # gRPC protocol definitions
|
||||||
|
│ ├── camera.proto
|
||||||
|
│ ├── stream.proto
|
||||||
|
│ ├── event.proto
|
||||||
|
│ ├── recording.proto
|
||||||
|
│ └── analytics.proto
|
||||||
|
│
|
||||||
├── tests/
|
├── tests/
|
||||||
│ ├── unit/
|
│ ├── api/
|
||||||
│ │ ├── test_auth_service.py
|
│ │ ├── unit/ # Unit tests for Python services
|
||||||
│ │ ├── test_sdk_bridge.py
|
│ │ │ ├── test_auth_service.py
|
||||||
│ │ ├── test_camera_service.py
|
│ │ │ ├── test_camera_service.py
|
||||||
│ │ └── test_permissions.py
|
│ │ │ └── test_event_service.py
|
||||||
│ ├── integration/
|
│ │ ├── integration/ # Integration tests with SDK bridge
|
||||||
│ │ ├── test_auth_endpoints.py
|
│ │ │ ├── test_camera_operations.py
|
||||||
│ │ ├── test_camera_endpoints.py
|
│ │ │ ├── test_stream_lifecycle.py
|
||||||
│ │ ├── test_event_endpoints.py
|
│ │ │ └── test_event_notifications.py
|
||||||
│ │ ├── test_recording_endpoints.py
|
│ │ └── contract/ # OpenAPI contract validation
|
||||||
│ │ └── test_websocket.py
|
│ │ └── test_openapi_compliance.py
|
||||||
│ ├── e2e/
|
│ │
|
||||||
│ │ └── test_user_workflows.py # End-to-end scenarios
|
│ └── sdk-bridge/
|
||||||
│ ├── conftest.py # Pytest fixtures
|
│ ├── Unit/ # C# unit tests
|
||||||
│ └── mocks/
|
│ │ ├── CameraServiceTests.cs
|
||||||
│ └── sdk_mock.py # Mock SDK for testing
|
│ │ └── StateQueryTests.cs
|
||||||
|
│ └── Integration/ # Tests with actual SDK
|
||||||
|
│ └── SdkIntegrationTests.cs
|
||||||
|
│
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── api/ # API documentation
|
│ ├── architecture.md # System architecture diagram
|
||||||
│ ├── deployment/ # Deployment guides
|
│ ├── sdk-integration.md # SDK integration patterns
|
||||||
│ └── sdk-mapping.md # GeViScope action → endpoint mapping
|
│ └── deployment.md # Production deployment guide
|
||||||
├── docker/
|
│
|
||||||
│ ├── Dockerfile # Windows container
|
├── scripts/
|
||||||
│ └── docker-compose.yml # Development environment
|
│ ├── setup_dev_environment.ps1 # Development environment setup
|
||||||
├── .env.example # Environment variable template
|
│ ├── start_services.ps1 # Start all services (Redis, SDK Bridge, API)
|
||||||
├── requirements.txt # Python dependencies
|
│ └── run_tests.sh # Test execution script
|
||||||
├── pyproject.toml # Project metadata, tool config
|
│
|
||||||
├── README.md # Project overview
|
├── .env.example # Environment variable template
|
||||||
└── .gitignore
|
├── requirements.txt # Python dependencies
|
||||||
|
├── pyproject.toml # Python project configuration
|
||||||
|
├── alembic.ini # Database migration configuration
|
||||||
|
└── README.md # Project overview
|
||||||
```
|
```
|
||||||
|
|
||||||
**Structure Decision**: Single project structure selected because this is an API-only service. Frontend/mobile clients will be separate projects that consume this API. The structure separates concerns into:
|
**Structure Decision**: Web application structure selected (backend API + SDK bridge service) because:
|
||||||
- `api/` - FastAPI routes, middleware, WebSocket
|
1. SDK requires Windows runtime → isolated C# bridge service
|
||||||
- `sdk/` - GeViScope SDK abstraction and translation
|
2. API layer can run on Linux → flexibility for deployment
|
||||||
- `services/` - Business logic layer
|
3. Clear separation between SDK complexity and API logic
|
||||||
- `models/` - Domain entities
|
4. gRPC provides high-performance, typed communication between layers
|
||||||
- `database/` - Data access layer
|
5. Python layer handles web concerns (HTTP, WebSocket, auth, validation)
|
||||||
- `core/` - Cross-cutting concerns (config, security, logging)
|
|
||||||
- `utils/` - Shared utilities
|
## Phase 0 - Research ✅ COMPLETED
|
||||||
|
|
||||||
|
**Deliverable**: [research.md](./research.md)
|
||||||
|
|
||||||
|
**Key Decisions**:
|
||||||
|
1. **SDK Integration Method**: C# gRPC bridge service (not pythonnet, subprocess, or COM)
|
||||||
|
- Rationale: Isolates SDK crashes, maintains type safety, enables independent scaling
|
||||||
|
2. **Stream Architecture**: Direct RTSP URLs with token authentication (not API proxy)
|
||||||
|
- Rationale: Reduces API latency, leverages existing streaming infrastructure
|
||||||
|
3. **Event Distribution**: FastAPI WebSocket + Redis Pub/Sub
|
||||||
|
- Rationale: Supports 1000+ concurrent connections, horizontal scaling capability
|
||||||
|
4. **Authentication**: JWT with Redis session storage
|
||||||
|
- Rationale: Stateless validation, flexible permissions, Redis for quick invalidation
|
||||||
|
5. **Performance Strategy**: Async Python + gRPC connection pooling
|
||||||
|
- Rationale: Non-blocking I/O for concurrent operations, <200ms response targets
|
||||||
|
|
||||||
|
**Critical Discoveries**:
|
||||||
|
- Visual C++ 2010 Redistributable (x86) mandatory for SDK DLL loading
|
||||||
|
- Full GeViSoft installation required (not just SDK)
|
||||||
|
- Windows Forms context needed for mixed-mode C++/CLI assemblies
|
||||||
|
- GeViServer ports: 7700, 7701, 7703 (NOT 7707 as initially assumed)
|
||||||
|
- SDK connection pattern: Create → RegisterCallback → Connect (order matters!)
|
||||||
|
- State Queries use GetFirst/GetNext iteration for enumerating entities
|
||||||
|
|
||||||
|
See [SDK_INTEGRATION_LESSONS.md](../../SDK_INTEGRATION_LESSONS.md) for complete details.
|
||||||
|
|
||||||
|
## Phase 1 - Design ✅ COMPLETED
|
||||||
|
|
||||||
|
**Deliverables**:
|
||||||
|
- [data-model.md](./data-model.md) - Entity schemas, relationships, validation rules
|
||||||
|
- [contracts/openapi.yaml](./contracts/openapi.yaml) - Complete REST API specification
|
||||||
|
- [quickstart.md](./quickstart.md) - Developer quick start guide
|
||||||
|
|
||||||
|
**Key Components**:
|
||||||
|
|
||||||
|
### Data Model
|
||||||
|
- **User**: Authentication, RBAC (viewer/operator/administrator), permissions
|
||||||
|
- **Camera**: Channel-based, capabilities (PTZ, analytics), status tracking
|
||||||
|
- **Stream**: Active sessions with token-authenticated URLs
|
||||||
|
- **Event**: Surveillance occurrences (motion, alarms, analytics)
|
||||||
|
- **Recording**: Video segments with ring buffer management
|
||||||
|
- **AnalyticsConfig**: VMD, NPR, OBTRACK configuration per camera
|
||||||
|
|
||||||
|
### API Endpoints (RESTful)
|
||||||
|
- `POST /api/v1/auth/login` - Authenticate and get JWT tokens
|
||||||
|
- `POST /api/v1/auth/refresh` - Refresh access token
|
||||||
|
- `POST /api/v1/auth/logout` - Invalidate tokens
|
||||||
|
- `GET /api/v1/cameras` - List cameras with filtering
|
||||||
|
- `GET /api/v1/cameras/{id}` - Get camera details
|
||||||
|
- `POST /api/v1/cameras/{id}/stream` - Start video stream
|
||||||
|
- `DELETE /api/v1/cameras/{id}/stream/{stream_id}` - Stop stream
|
||||||
|
- `POST /api/v1/cameras/{id}/ptz` - PTZ control commands
|
||||||
|
- `WS /api/v1/events/stream` - WebSocket event notifications
|
||||||
|
- `GET /api/v1/events` - Query event history
|
||||||
|
- `GET /api/v1/recordings` - Query recordings
|
||||||
|
- `POST /api/v1/recordings/{id}/export` - Export video segment
|
||||||
|
- `GET /api/v1/analytics/{camera_id}` - Get analytics configuration
|
||||||
|
- `POST /api/v1/analytics/{camera_id}` - Configure analytics
|
||||||
|
- `GET /api/v1/health` - System health check
|
||||||
|
- `GET /api/v1/status` - Detailed system status
|
||||||
|
|
||||||
|
### gRPC Service Definitions
|
||||||
|
- **CameraService**: ListCameras, GetCameraDetails, GetCameraStatus
|
||||||
|
- **StreamService**: StartStream, StopStream, GetStreamStatus
|
||||||
|
- **PTZService**: MoveCamera, SetPreset, GotoPreset
|
||||||
|
- **EventService**: SubscribeEvents, UnsubscribeEvents (server streaming)
|
||||||
|
- **RecordingService**: QueryRecordings, StartRecording, StopRecording
|
||||||
|
- **AnalyticsService**: ConfigureAnalytics, GetAnalyticsConfig
|
||||||
|
|
||||||
|
## Phase 2 - Tasks ⏭️ NEXT
|
||||||
|
|
||||||
|
**Command**: `/speckit.tasks`
|
||||||
|
|
||||||
|
Will generate:
|
||||||
|
- Task breakdown with dependencies
|
||||||
|
- Implementation order (TDD-first)
|
||||||
|
- Test plan for each task
|
||||||
|
- Acceptance criteria per task
|
||||||
|
- Time estimates
|
||||||
|
|
||||||
|
**Expected Task Categories**:
|
||||||
|
1. **Infrastructure Setup**: Repository structure, development environment, CI/CD
|
||||||
|
2. **SDK Bridge Foundation**: gRPC server, SDK wrapper, basic camera queries
|
||||||
|
3. **API Foundation**: FastAPI app, authentication, middleware
|
||||||
|
4. **Core Features**: Camera management, stream lifecycle, event notifications
|
||||||
|
5. **Extended Features**: Recording management, analytics configuration
|
||||||
|
6. **Testing & Documentation**: Contract tests, integration tests, deployment docs
|
||||||
|
|
||||||
|
## Phase 3 - Implementation ⏭️ FUTURE
|
||||||
|
|
||||||
|
**Command**: `/speckit.implement`
|
||||||
|
|
||||||
|
Will execute TDD implementation:
|
||||||
|
- Red: Write failing test
|
||||||
|
- Green: Minimal code to pass test
|
||||||
|
- Refactor: Clean up while maintaining passing tests
|
||||||
|
- Repeat for each task
|
||||||
|
|
||||||
## Complexity Tracking
|
## Complexity Tracking
|
||||||
|
|
||||||
**No constitution violations requiring justification.**
|
No constitution violations. All design decisions follow simplicity and clarity principles:
|
||||||
|
- ✅ REST over custom protocols
|
||||||
|
- ✅ JWT over session management
|
||||||
|
- ✅ Direct streaming over proxying
|
||||||
|
- ✅ Clear layer separation (API ↔ Bridge ↔ SDK)
|
||||||
|
- ✅ Standard patterns (FastAPI, gRPC, SQLAlchemy)
|
||||||
|
|
||||||
All technical choices align with constitution principles. The selected technology stack (Python + FastAPI + Redis) directly implements the decisions made in the constitution.
|
## Technology Stack Summary
|
||||||
|
|
||||||
## Phase 0: Research & Technical Decisions
|
### Python API Layer
|
||||||
|
- **Web Framework**: FastAPI 0.104+
|
||||||
|
- **ASGI Server**: Uvicorn with uvloop
|
||||||
|
- **ORM**: SQLAlchemy 2.0+
|
||||||
|
- **Database**: PostgreSQL 14+
|
||||||
|
- **Cache/PubSub**: Redis 6.0+ (aioredis)
|
||||||
|
- **Authentication**: PyJWT, passlib (bcrypt)
|
||||||
|
- **gRPC Client**: grpcio, protobuf
|
||||||
|
- **Validation**: Pydantic v2
|
||||||
|
- **Testing**: pytest, pytest-asyncio, httpx
|
||||||
|
- **Code Quality**: ruff (linting), black (formatting), mypy (type checking)
|
||||||
|
|
||||||
**Status**: Pending - To be completed in research.md
|
### C# SDK Bridge
|
||||||
|
- **Framework**: .NET Framework 4.8 (SDK runtime), .NET 8.0 (gRPC service)
|
||||||
|
- **gRPC**: Grpc.Core, Grpc.Tools
|
||||||
|
- **SDK**: GeViScope SDK 7.9.975.68+ (GeViProcAPINET_4_0.dll)
|
||||||
|
- **Testing**: xUnit, Moq
|
||||||
|
- **Logging**: Serilog
|
||||||
|
|
||||||
### Research Topics
|
### Infrastructure
|
||||||
|
- **Database**: PostgreSQL 14+ (user data, audit logs)
|
||||||
|
- **Cache**: Redis 6.0+ (sessions, pub/sub)
|
||||||
|
- **Deployment**: Docker (API layer), Windows Service (SDK bridge)
|
||||||
|
- **CI/CD**: GitHub Actions
|
||||||
|
- **Monitoring**: Prometheus metrics, Grafana dashboards
|
||||||
|
|
||||||
1. **GeViScope SDK Integration**
|
## Commands Reference
|
||||||
- Research COM/DLL interface patterns for Python (pywin32 vs comtypes)
|
|
||||||
- Document GeViScope SDK action categories and parameters
|
|
||||||
- Identify SDK event notification mechanisms
|
|
||||||
- Determine video stream URL/protocol format
|
|
||||||
|
|
||||||
2. **Video Streaming Strategy**
|
### Development
|
||||||
- Research options: Direct URLs vs API proxy vs WebRTC signaling
|
```bash
|
||||||
- Evaluate bandwidth implications for 100+ concurrent streams
|
# Setup environment
|
||||||
- Determine authentication method for video streams
|
.\scripts\setup_dev_environment.ps1
|
||||||
- Document GeViScope streaming protocols
|
|
||||||
|
|
||||||
3. **WebSocket Event Architecture**
|
# Start all services
|
||||||
- Research FastAPI WebSocket best practices for 1000+ connections
|
.\scripts\start_services.ps1
|
||||||
- Design event subscription patterns (by type, by channel, by user)
|
|
||||||
- Determine connection lifecycle management (heartbeat, reconnection)
|
|
||||||
- Plan message batching strategy for high-frequency events
|
|
||||||
|
|
||||||
4. **Authentication & Session Management**
|
# Run API server (development)
|
||||||
- Finalize JWT token structure and claims
|
cd src/api
|
||||||
- Design refresh token rotation strategy
|
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||||
- Plan API key generation and storage (for service accounts)
|
|
||||||
- Determine Redis session schema and TTL values
|
|
||||||
|
|
||||||
5. **Performance Optimization**
|
# Run SDK bridge (development)
|
||||||
- Research async patterns for SDK I/O operations
|
cd src/sdk-bridge
|
||||||
- Plan connection pooling strategy for SDK
|
dotnet run --configuration Debug
|
||||||
- Design caching strategy for camera metadata
|
|
||||||
- Evaluate load balancing options (horizontal scaling)
|
|
||||||
|
|
||||||
6. **Error Handling & Monitoring**
|
# Run tests
|
||||||
- Map Windows error codes to HTTP status codes
|
pytest tests/api -v --cov=src/api --cov-report=html # Python
|
||||||
- Design structured logging format
|
dotnet test tests/sdk-bridge/ # C#
|
||||||
- Plan health check implementation (SDK connectivity, Redis, resource usage)
|
|
||||||
- Identify metrics to expose (Prometheus format)
|
|
||||||
|
|
||||||
7. **Testing Strategy**
|
# Format code
|
||||||
- Design SDK mock implementation for tests without hardware
|
ruff check src/api --fix # Python linting
|
||||||
- Plan test data generation (sample cameras, events, recordings)
|
black src/api # Python formatting
|
||||||
- Determine integration test approach (test SDK instance vs mocks)
|
|
||||||
- Document E2E test scenarios
|
|
||||||
|
|
||||||
**Output Location**: `specs/001-surveillance-api/research.md`
|
# Database migrations
|
||||||
|
alembic upgrade head # Apply migrations
|
||||||
|
alembic revision --autogenerate -m "description" # Create migration
|
||||||
|
```
|
||||||
|
|
||||||
## Phase 1: Design & Contracts
|
### API Usage
|
||||||
|
```bash
|
||||||
|
# Authenticate
|
||||||
|
curl -X POST http://localhost:8000/api/v1/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username": "sysadmin", "password": "masterkey"}'
|
||||||
|
|
||||||
**Status**: Pending - To be completed after Phase 0 research
|
# List cameras
|
||||||
|
curl -X GET http://localhost:8000/api/v1/cameras \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN"
|
||||||
|
|
||||||
### Deliverables
|
# Start stream
|
||||||
|
curl -X POST http://localhost:8000/api/v1/cameras/{id}/stream \
|
||||||
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"resolution": {"width": 1920, "height": 1080, "fps": 30}, "format": "h264"}'
|
||||||
|
|
||||||
1. **Data Model** (`data-model.md`)
|
# WebSocket events (Python)
|
||||||
- Entity schemas (User, Camera, Event, Recording, Stream, etc.)
|
import websockets
|
||||||
- Validation rules
|
uri = f"ws://localhost:8000/api/v1/events/stream?token={TOKEN}"
|
||||||
- State transitions (e.g., Recording states: idle → recording → stopped)
|
async with websockets.connect(uri) as ws:
|
||||||
- Relationships and foreign keys
|
await ws.send('{"action": "subscribe", "filters": {"event_types": ["motion_detected"]}}')
|
||||||
|
while True:
|
||||||
2. **API Contracts** (`contracts/openapi.yaml`)
|
event = await ws.recv()
|
||||||
- Complete OpenAPI 3.0 specification
|
print(event)
|
||||||
- All endpoints with request/response schemas
|
|
||||||
- Authentication scheme definitions
|
|
||||||
- WebSocket protocol documentation
|
|
||||||
- Error response formats
|
|
||||||
|
|
||||||
3. **Quick Start Guide** (`quickstart.md`)
|
|
||||||
- Installation instructions
|
|
||||||
- Configuration guide (environment variables)
|
|
||||||
- First API call example (authentication)
|
|
||||||
- Common use cases with curl/Python examples
|
|
||||||
|
|
||||||
4. **Agent Context Update**
|
|
||||||
- Run `.specify/scripts/powershell/update-agent-context.ps1 -AgentType claude`
|
|
||||||
- Add project-specific context to Claude agent file
|
|
||||||
|
|
||||||
### API Endpoint Overview (Design Phase)
|
|
||||||
|
|
||||||
**Authentication** (`/api/v1/auth/`):
|
|
||||||
- `POST /login` - Obtain JWT token
|
|
||||||
- `POST /refresh` - Refresh access token
|
|
||||||
- `POST /logout` - Invalidate session
|
|
||||||
|
|
||||||
**Cameras** (`/api/v1/cameras/`):
|
|
||||||
- `GET /` - List all cameras (filtered by permissions)
|
|
||||||
- `GET /{id}` - Get camera details
|
|
||||||
- `GET /{id}/stream` - Get live video stream URL/connection
|
|
||||||
- `POST /{id}/ptz` - Send PTZ command
|
|
||||||
- `GET /{id}/presets` - Get PTZ presets
|
|
||||||
- `POST /{id}/presets` - Save PTZ preset
|
|
||||||
|
|
||||||
**Events** (`/api/v1/events/`):
|
|
||||||
- `WS /stream` - WebSocket endpoint for event subscriptions
|
|
||||||
- `GET /` - Query event history (paginated)
|
|
||||||
- `GET /{id}` - Get event details
|
|
||||||
|
|
||||||
**Recordings** (`/api/v1/recordings/`):
|
|
||||||
- `GET /` - Query recordings by channel/time range
|
|
||||||
- `POST /{channel}/start` - Start recording
|
|
||||||
- `POST /{channel}/stop` - Stop recording
|
|
||||||
- `GET /{id}` - Get recording details
|
|
||||||
- `POST /{id}/export` - Request video export
|
|
||||||
- `GET /capacity` - Get recording capacity metrics
|
|
||||||
|
|
||||||
**Analytics** (`/api/v1/analytics/`):
|
|
||||||
- `GET /{channel}/config` - Get analytics configuration
|
|
||||||
- `PUT /{channel}/config` - Update analytics configuration
|
|
||||||
- `POST /{channel}/vmd` - Configure motion detection
|
|
||||||
- `POST /{channel}/npr` - Configure license plate recognition
|
|
||||||
- `POST /{channel}/obtrack` - Configure object tracking
|
|
||||||
|
|
||||||
**System** (`/api/v1/system/`):
|
|
||||||
- `GET /health` - Health check (no auth required)
|
|
||||||
- `GET /status` - Detailed system status
|
|
||||||
- `GET /metrics` - Prometheus metrics
|
|
||||||
|
|
||||||
**Output Locations**:
|
|
||||||
- `specs/001-surveillance-api/data-model.md`
|
|
||||||
- `specs/001-surveillance-api/contracts/openapi.yaml`
|
|
||||||
- `specs/001-surveillance-api/quickstart.md`
|
|
||||||
|
|
||||||
## Phase 2: Task Breakdown
|
|
||||||
|
|
||||||
**Not created by `/speckit.plan`** - This phase is handled by `/speckit.tasks` command
|
|
||||||
|
|
||||||
The tasks phase will break down the implementation into concrete work items organized by:
|
|
||||||
- Setup phase (project scaffolding, dependencies)
|
|
||||||
- Foundational phase (SDK bridge, authentication, database)
|
|
||||||
- User story phases (P1, P2, P3 stories as separate task groups)
|
|
||||||
- Polish phase (documentation, optimization, security hardening)
|
|
||||||
|
|
||||||
## Deployment Considerations
|
|
||||||
|
|
||||||
### Development Environment
|
|
||||||
- Python 3.11+ installed
|
|
||||||
- GeViScope SDK installed and configured
|
|
||||||
- Redis running locally or via Docker (Windows containers)
|
|
||||||
- Environment variables configured (.env file)
|
|
||||||
|
|
||||||
### Production Environment
|
|
||||||
- Windows Server 2016+ or Windows 10/11
|
|
||||||
- GeViScope SDK with active license
|
|
||||||
- Redis cluster or managed instance
|
|
||||||
- TLS certificates configured
|
|
||||||
- Reverse proxy (nginx/IIS) for HTTPS termination
|
|
||||||
- Environment variables via system config or key vault
|
|
||||||
|
|
||||||
### Configuration Management
|
|
||||||
All configuration via environment variables:
|
|
||||||
- `SDK_CONNECTION_STRING` - GeViScope SDK connection details
|
|
||||||
- `JWT_SECRET_KEY` - JWT signing key
|
|
||||||
- `JWT_ALGORITHM` - Default: HS256
|
|
||||||
- `JWT_EXPIRATION_MINUTES` - Default: 60
|
|
||||||
- `REDIS_URL` - Redis connection URL
|
|
||||||
- `LOG_LEVEL` - Logging level (DEBUG, INFO, WARNING, ERROR)
|
|
||||||
- `CORS_ORIGINS` - Allowed CORS origins for web clients
|
|
||||||
- `MAX_CONCURRENT_STREAMS` - Concurrent stream limit
|
|
||||||
- `RATE_LIMIT_AUTH` - Auth endpoint rate limit
|
|
||||||
|
|
||||||
### Docker Deployment
|
|
||||||
```dockerfile
|
|
||||||
# Windows Server Core base image
|
|
||||||
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
|
||||||
|
|
||||||
# Install Python 3.11
|
|
||||||
# Install GeViScope SDK
|
|
||||||
# Copy application code
|
|
||||||
# Install Python dependencies
|
|
||||||
# Expose ports 8000 (HTTP), 8001 (WebSocket)
|
|
||||||
# Run uvicorn server
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
1. ✅ Constitution defined and validated
|
1. **Run `/speckit.tasks`** to generate Phase 2 task breakdown
|
||||||
2. ✅ Specification created with user stories and requirements
|
2. **Review tasks** for sequencing and dependencies
|
||||||
3. ✅ Implementation plan created (this document)
|
3. **Execute `/speckit.implement`** to begin TDD implementation
|
||||||
4. ⏭️ **Execute `/speckit.plan` Phase 0**: Generate research.md
|
4. **Iterate** through tasks following Red-Green-Refactor cycle
|
||||||
5. ⏭️ **Execute `/speckit.plan` Phase 1**: Generate data-model.md, contracts/, quickstart.md
|
|
||||||
6. ⏭️ **Execute `/speckit.tasks`**: Break down into actionable task list
|
## References
|
||||||
7. ⏭️ **Execute `/speckit.implement`**: Begin TDD implementation
|
|
||||||
|
- **Specification**: [spec.md](./spec.md) - User stories, requirements, success criteria
|
||||||
|
- **Research**: [research.md](./research.md) - Technical decisions and architectural analysis
|
||||||
|
- **Data Model**: [data-model.md](./data-model.md) - Entity schemas and relationships
|
||||||
|
- **API Contract**: [contracts/openapi.yaml](./contracts/openapi.yaml) - Complete REST API spec
|
||||||
|
- **Quick Start**: [quickstart.md](./quickstart.md) - Developer onboarding guide
|
||||||
|
- **SDK Lessons**: [../../SDK_INTEGRATION_LESSONS.md](../../SDK_INTEGRATION_LESSONS.md) - Critical SDK integration knowledge
|
||||||
|
- **Constitution**: [../../.specify/memory/constitution.md](../../.specify/memory/constitution.md) - Development principles
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Plan Status**: ✅ Technical plan complete, ready for Phase 0 research
|
**Plan Status**: Phase 0 ✅ | Phase 1 ✅ | Phase 2 ⏭️ | Phase 3 ⏭️
|
||||||
**Constitution Compliance**: ✅ All gates passed
|
**Last Updated**: 2025-12-08
|
||||||
**Next Command**: Continue with research phase to resolve implementation details
|
|
||||||
|
|||||||
700
specs/001-surveillance-api/quickstart.md
Normal file
700
specs/001-surveillance-api/quickstart.md
Normal file
@@ -0,0 +1,700 @@
|
|||||||
|
# Quick Start Guide
|
||||||
|
|
||||||
|
**Geutebruck Surveillance API** - REST API for GeViScope/GeViSoft video surveillance systems
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This API provides RESTful access to Geutebruck surveillance systems, enabling:
|
||||||
|
|
||||||
|
- **Camera Management**: List cameras, get status, control PTZ
|
||||||
|
- **Live Streaming**: Start/stop video streams with token authentication
|
||||||
|
- **Event Monitoring**: Subscribe to real-time surveillance events (motion, alarms, analytics)
|
||||||
|
- **Recording Access**: Query and export recorded video segments
|
||||||
|
- **Analytics Configuration**: Configure video analytics (VMD, NPR, object tracking)
|
||||||
|
|
||||||
|
**Architecture**: Python FastAPI + C# gRPC SDK Bridge + GeViScope SDK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
- **Operating System**: Windows 10/11 or Windows Server 2016+
|
||||||
|
- **GeViSoft Installation**: Full GeViSoft application + SDK
|
||||||
|
- **Visual C++ 2010 Redistributable (x86)**: Required for SDK
|
||||||
|
- **Python**: 3.11+ (for API server)
|
||||||
|
- **.NET Framework**: 4.8 (for SDK bridge)
|
||||||
|
- **Redis**: 6.0+ (for session management and pub/sub)
|
||||||
|
|
||||||
|
### GeViSoft SDK Setup
|
||||||
|
|
||||||
|
**CRITICAL**: Install in this exact order:
|
||||||
|
|
||||||
|
1. **Install Visual C++ 2010 Redistributable (x86)**
|
||||||
|
```powershell
|
||||||
|
# Download and install
|
||||||
|
Invoke-WebRequest -Uri 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe' -OutFile 'vcredist_x86_2010.exe'
|
||||||
|
Start-Process -FilePath 'vcredist_x86_2010.exe' -ArgumentList '/install', '/quiet', '/norestart' -Wait
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install GeViSoft Full Application**
|
||||||
|
- Download from Geutebruck
|
||||||
|
- Run installer
|
||||||
|
- Complete setup wizard
|
||||||
|
|
||||||
|
3. **Install GeViSoft SDK**
|
||||||
|
- Download SDK installer
|
||||||
|
- Run SDK setup
|
||||||
|
- Verify installation in `C:\Program Files (x86)\GeViScopeSDK\`
|
||||||
|
|
||||||
|
4. **Start GeViServer**
|
||||||
|
```cmd
|
||||||
|
cd C:\GEVISOFT
|
||||||
|
GeViServer.exe console
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification**:
|
||||||
|
```powershell
|
||||||
|
# Check GeViServer is running
|
||||||
|
netstat -an | findstr "7700 7701 7703"
|
||||||
|
# Should show LISTENING on these ports
|
||||||
|
```
|
||||||
|
|
||||||
|
See [SDK_INTEGRATION_LESSONS.md](../../SDK_INTEGRATION_LESSONS.md) for complete deployment details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Clone Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/geutebruck-api.git
|
||||||
|
cd geutebruck-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install Dependencies
|
||||||
|
|
||||||
|
**Python API Server**:
|
||||||
|
```bash
|
||||||
|
cd src/api
|
||||||
|
python -m venv venv
|
||||||
|
venv\Scripts\activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**C# SDK Bridge**:
|
||||||
|
```bash
|
||||||
|
cd src/sdk-bridge
|
||||||
|
dotnet restore
|
||||||
|
dotnet build --configuration Release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install Redis
|
||||||
|
|
||||||
|
**Using Chocolatey**:
|
||||||
|
```powershell
|
||||||
|
choco install redis-64
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Or download from: https://redis.io/download
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
Create `.env` file in `src/api/`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# API Configuration
|
||||||
|
API_HOST=0.0.0.0
|
||||||
|
API_PORT=8000
|
||||||
|
API_TITLE=Geutebruck Surveillance API
|
||||||
|
API_VERSION=1.0.0
|
||||||
|
|
||||||
|
# GeViScope Connection
|
||||||
|
GEVISCOPE_HOST=localhost
|
||||||
|
GEVISCOPE_USERNAME=sysadmin
|
||||||
|
GEVISCOPE_PASSWORD=masterkey
|
||||||
|
|
||||||
|
# SDK Bridge gRPC
|
||||||
|
SDK_BRIDGE_HOST=localhost
|
||||||
|
SDK_BRIDGE_PORT=50051
|
||||||
|
|
||||||
|
# Redis Configuration
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DB=0
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
|
||||||
|
# JWT Authentication
|
||||||
|
JWT_SECRET_KEY=your-secret-key-change-in-production
|
||||||
|
JWT_ALGORITHM=HS256
|
||||||
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||||
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
|
||||||
|
# Stream URLs
|
||||||
|
STREAM_BASE_URL=rtsp://localhost:8554
|
||||||
|
STREAM_TOKEN_EXPIRE_MINUTES=15
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_FORMAT=json
|
||||||
|
```
|
||||||
|
|
||||||
|
**Security Note**: Change `JWT_SECRET_KEY` and `GEVISCOPE_PASSWORD` in production!
|
||||||
|
|
||||||
|
### Database Migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/api
|
||||||
|
alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Starting the Services
|
||||||
|
|
||||||
|
### 1. Start GeViServer
|
||||||
|
```cmd
|
||||||
|
cd C:\GEVISOFT
|
||||||
|
GeViServer.exe console
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start Redis
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start SDK Bridge
|
||||||
|
```bash
|
||||||
|
cd src/sdk-bridge
|
||||||
|
dotnet run --configuration Release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Start API Server
|
||||||
|
```bash
|
||||||
|
cd src/api
|
||||||
|
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify Services**:
|
||||||
|
- API: http://localhost:8000/api/v1/health
|
||||||
|
- API Docs: http://localhost:8000/docs
|
||||||
|
- SDK Bridge: gRPC on localhost:50051
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## First API Call
|
||||||
|
|
||||||
|
### 1. Authenticate
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"username": "sysadmin",
|
||||||
|
"password": "masterkey"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Save the access token** - you'll need it for all subsequent requests.
|
||||||
|
|
||||||
|
### 2. List Cameras
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
```bash
|
||||||
|
curl -X GET "http://localhost:8000/api/v1/cameras" \
|
||||||
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 2,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 50,
|
||||||
|
"cameras": [
|
||||||
|
{
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440001",
|
||||||
|
"channel": 1,
|
||||||
|
"name": "Front Entrance",
|
||||||
|
"description": "Main entrance camera",
|
||||||
|
"status": "online",
|
||||||
|
"capabilities": {
|
||||||
|
"ptz": true,
|
||||||
|
"audio": false,
|
||||||
|
"analytics": ["motion_detection", "people_counting"]
|
||||||
|
},
|
||||||
|
"resolutions": [
|
||||||
|
{"width": 1920, "height": 1080, "fps": 30},
|
||||||
|
{"width": 1280, "height": 720, "fps": 60}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start Video Stream
|
||||||
|
|
||||||
|
**Request**:
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/cameras/550e8400-e29b-41d4-a716-446655440001/stream" \
|
||||||
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"resolution": {"width": 1920, "height": 1080, "fps": 30},
|
||||||
|
"format": "h264"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"stream_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
||||||
|
"camera_id": "550e8400-e29b-41d4-a716-446655440001",
|
||||||
|
"url": "rtsp://localhost:8554/stream/7c9e6679?token=eyJhbGc...",
|
||||||
|
"format": "h264",
|
||||||
|
"resolution": {"width": 1920, "height": 1080, "fps": 30},
|
||||||
|
"started_at": "2025-12-08T15:30:00Z",
|
||||||
|
"expires_at": "2025-12-08T15:45:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use the stream URL** in your video player (VLC, ffplay, etc.):
|
||||||
|
```bash
|
||||||
|
ffplay "rtsp://localhost:8554/stream/7c9e6679?token=eyJhbGc..."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Python SDK Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
class GeutebruckAPI:
|
||||||
|
def __init__(self, base_url: str = "http://localhost:8000"):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.access_token = None
|
||||||
|
|
||||||
|
def login(self, username: str, password: str) -> Dict[str, Any]:
|
||||||
|
"""Authenticate and get access token"""
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/api/v1/auth/login",
|
||||||
|
json={"username": username, "password": password}
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
self.access_token = data["access_token"]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_cameras(self) -> Dict[str, Any]:
|
||||||
|
"""List all cameras"""
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.base_url}/api/v1/cameras",
|
||||||
|
headers={"Authorization": f"Bearer {self.access_token}"}
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def start_stream(self, camera_id: str, width: int = 1920, height: int = 1080) -> Dict[str, Any]:
|
||||||
|
"""Start video stream from camera"""
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/api/v1/cameras/{camera_id}/stream",
|
||||||
|
headers={"Authorization": f"Bearer {self.access_token}"},
|
||||||
|
json={
|
||||||
|
"resolution": {"width": width, "height": height, "fps": 30},
|
||||||
|
"format": "h264"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
api = GeutebruckAPI()
|
||||||
|
api.login("sysadmin", "masterkey")
|
||||||
|
cameras = api.get_cameras()
|
||||||
|
stream = api.start_stream(cameras["cameras"][0]["id"])
|
||||||
|
print(f"Stream URL: {stream['url']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebSocket Event Monitoring
|
||||||
|
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
import json
|
||||||
|
|
||||||
|
async def monitor_events(access_token: str):
|
||||||
|
"""Subscribe to real-time surveillance events"""
|
||||||
|
uri = f"ws://localhost:8000/api/v1/events/stream?token={access_token}"
|
||||||
|
|
||||||
|
async with websockets.connect(uri) as websocket:
|
||||||
|
# Subscribe to specific event types
|
||||||
|
await websocket.send(json.dumps({
|
||||||
|
"action": "subscribe",
|
||||||
|
"filters": {
|
||||||
|
"event_types": ["motion_detected", "alarm_triggered"],
|
||||||
|
"camera_ids": ["550e8400-e29b-41d4-a716-446655440001"]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
# Receive events
|
||||||
|
while True:
|
||||||
|
message = await websocket.recv()
|
||||||
|
event = json.loads(message)
|
||||||
|
print(f"Event: {event['event_type']} on camera {event['camera_id']}")
|
||||||
|
print(f" Timestamp: {event['timestamp']}")
|
||||||
|
print(f" Details: {event['details']}")
|
||||||
|
|
||||||
|
# Run
|
||||||
|
asyncio.run(monitor_events("YOUR_ACCESS_TOKEN"))
|
||||||
|
```
|
||||||
|
|
||||||
|
### PTZ Camera Control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Move camera to preset position
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/cameras/550e8400-e29b-41d4-a716-446655440001/ptz" \
|
||||||
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"command": "goto_preset",
|
||||||
|
"preset": 1
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Pan/tilt/zoom control
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/cameras/550e8400-e29b-41d4-a716-446655440001/ptz" \
|
||||||
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"command": "move",
|
||||||
|
"pan": 50,
|
||||||
|
"tilt": 30,
|
||||||
|
"zoom": 2.5,
|
||||||
|
"speed": 50
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query Recordings
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
def get_recordings(camera_id: str, access_token: str):
|
||||||
|
"""Get recordings from last 24 hours"""
|
||||||
|
end_time = datetime.utcnow()
|
||||||
|
start_time = end_time - timedelta(hours=24)
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
"http://localhost:8000/api/v1/recordings",
|
||||||
|
headers={"Authorization": f"Bearer {access_token}"},
|
||||||
|
params={
|
||||||
|
"camera_id": camera_id,
|
||||||
|
"start_time": start_time.isoformat() + "Z",
|
||||||
|
"end_time": end_time.isoformat() + "Z",
|
||||||
|
"event_type": "motion_detected"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
recordings = get_recordings("550e8400-e29b-41d4-a716-446655440001", "YOUR_ACCESS_TOKEN")
|
||||||
|
for rec in recordings["recordings"]:
|
||||||
|
print(f"Recording: {rec['start_time']} - {rec['end_time']}")
|
||||||
|
print(f" Size: {rec['size_bytes'] / 1024 / 1024:.2f} MB")
|
||||||
|
print(f" Export URL: {rec['export_url']}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure Video Analytics
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable motion detection
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/analytics/550e8400-e29b-41d4-a716-446655440001" \
|
||||||
|
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"type": "motion_detection",
|
||||||
|
"enabled": true,
|
||||||
|
"configuration": {
|
||||||
|
"sensitivity": 75,
|
||||||
|
"regions": [
|
||||||
|
{
|
||||||
|
"name": "entrance",
|
||||||
|
"points": [
|
||||||
|
{"x": 100, "y": 100},
|
||||||
|
{"x": 500, "y": 100},
|
||||||
|
{"x": 500, "y": 400},
|
||||||
|
{"x": 100, "y": 400}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schedule": {
|
||||||
|
"enabled": true,
|
||||||
|
"start_time": "18:00:00",
|
||||||
|
"end_time": "06:00:00",
|
||||||
|
"days": [1, 2, 3, 4, 5, 6, 7]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Run Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/api
|
||||||
|
pytest tests/unit -v --cov=app --cov-report=html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Requires running GeViServer and SDK Bridge
|
||||||
|
pytest tests/integration -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage
|
||||||
|
|
||||||
|
Minimum 80% coverage enforced. View coverage report:
|
||||||
|
```bash
|
||||||
|
# Open coverage report
|
||||||
|
start htmlcov/index.html # Windows
|
||||||
|
open htmlcov/index.html # macOS
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
### Interactive Docs
|
||||||
|
|
||||||
|
Once the API is running, visit:
|
||||||
|
|
||||||
|
- **Swagger UI**: http://localhost:8000/docs
|
||||||
|
- **ReDoc**: http://localhost:8000/redoc
|
||||||
|
- **OpenAPI JSON**: http://localhost:8000/openapi.json
|
||||||
|
|
||||||
|
### Complete API Reference
|
||||||
|
|
||||||
|
See [contracts/openapi.yaml](./contracts/openapi.yaml) for the complete OpenAPI 3.0 specification.
|
||||||
|
|
||||||
|
### Data Model
|
||||||
|
|
||||||
|
See [data-model.md](./data-model.md) for entity schemas, relationships, and validation rules.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
See [research.md](./research.md) for:
|
||||||
|
- System architecture decisions
|
||||||
|
- SDK integration patterns
|
||||||
|
- Performance considerations
|
||||||
|
- Security implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**1. "Could not load file or assembly 'GeViProcAPINET_4_0.dll'"**
|
||||||
|
|
||||||
|
**Solution**: Install Visual C++ 2010 Redistributable (x86):
|
||||||
|
```powershell
|
||||||
|
Invoke-WebRequest -Uri 'https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe' -OutFile 'vcredist_x86_2010.exe'
|
||||||
|
Start-Process -FilePath 'vcredist_x86_2010.exe' -ArgumentList '/install', '/quiet', '/norestart' -Wait
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. "Connection refused to GeViServer"**
|
||||||
|
|
||||||
|
**Solution**: Ensure GeViServer is running:
|
||||||
|
```cmd
|
||||||
|
cd C:\GEVISOFT
|
||||||
|
GeViServer.exe console
|
||||||
|
```
|
||||||
|
Check ports: `netstat -an | findstr "7700 7701 7703"`
|
||||||
|
|
||||||
|
**3. "Redis connection failed"**
|
||||||
|
|
||||||
|
**Solution**: Start Redis server:
|
||||||
|
```bash
|
||||||
|
redis-server
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. "SDK Bridge gRPC not responding"**
|
||||||
|
|
||||||
|
**Solution**: Check SDK Bridge logs and restart:
|
||||||
|
```bash
|
||||||
|
cd src/sdk-bridge
|
||||||
|
dotnet run --configuration Release
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. "401 Unauthorized" on API calls**
|
||||||
|
|
||||||
|
**Solution**: Check your access token hasn't expired (1 hour lifetime). Use refresh token to get new access token:
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:8000/api/v1/auth/refresh" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"refresh_token": "YOUR_REFRESH_TOKEN"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
Enable debug logging:
|
||||||
|
```env
|
||||||
|
LOG_LEVEL=DEBUG
|
||||||
|
```
|
||||||
|
|
||||||
|
View logs:
|
||||||
|
```bash
|
||||||
|
# API logs
|
||||||
|
tail -f logs/api.log
|
||||||
|
|
||||||
|
# SDK Bridge logs
|
||||||
|
tail -f src/sdk-bridge/logs/bridge.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API health
|
||||||
|
curl http://localhost:8000/api/v1/health
|
||||||
|
|
||||||
|
# Expected response
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"timestamp": "2025-12-08T15:30:00Z",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"sdk_bridge": "connected",
|
||||||
|
"redis": "connected",
|
||||||
|
"database": "connected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Tuning
|
||||||
|
|
||||||
|
### Response Time Optimization
|
||||||
|
|
||||||
|
**Target**: <200ms for most endpoints
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Connection pooling
|
||||||
|
SDK_BRIDGE_POOL_SIZE=10
|
||||||
|
SDK_BRIDGE_MAX_OVERFLOW=20
|
||||||
|
|
||||||
|
# Redis connection pool
|
||||||
|
REDIS_MAX_CONNECTIONS=50
|
||||||
|
|
||||||
|
# Async workers
|
||||||
|
UVICORN_WORKERS=4
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebSocket Scaling
|
||||||
|
|
||||||
|
**Target**: 1000+ concurrent connections
|
||||||
|
|
||||||
|
```env
|
||||||
|
# Redis pub/sub
|
||||||
|
REDIS_PUBSUB_MAX_CONNECTIONS=100
|
||||||
|
|
||||||
|
# WebSocket timeouts
|
||||||
|
WEBSOCKET_PING_INTERVAL=30
|
||||||
|
WEBSOCKET_PING_TIMEOUT=10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream URL Caching
|
||||||
|
|
||||||
|
Stream URLs are cached for token lifetime (15 minutes) to reduce SDK bridge calls.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Production Deployment
|
||||||
|
|
||||||
|
**CRITICAL**: Before deploying to production:
|
||||||
|
|
||||||
|
1. **Change default credentials**:
|
||||||
|
```env
|
||||||
|
GEVISCOPE_PASSWORD=your-secure-password-here
|
||||||
|
JWT_SECRET_KEY=generate-with-openssl-rand-hex-32
|
||||||
|
REDIS_PASSWORD=your-redis-password
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Enable HTTPS**:
|
||||||
|
- Use reverse proxy (nginx/Caddy) with SSL certificates
|
||||||
|
- Redirect HTTP to HTTPS
|
||||||
|
|
||||||
|
3. **Network security**:
|
||||||
|
- GeViServer should NOT be exposed to internet
|
||||||
|
- API should be behind firewall/VPN
|
||||||
|
- Use internal network for SDK Bridge ↔ GeViServer communication
|
||||||
|
|
||||||
|
4. **Rate limiting**:
|
||||||
|
```env
|
||||||
|
RATE_LIMIT_PER_MINUTE=60
|
||||||
|
RATE_LIMIT_BURST=10
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Audit logging**:
|
||||||
|
```env
|
||||||
|
AUDIT_LOG_ENABLED=true
|
||||||
|
AUDIT_LOG_PATH=/var/log/geutebruck-api/audit.log
|
||||||
|
```
|
||||||
|
|
||||||
|
See [security.md](./security.md) for complete security guidelines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Read the Architecture**: [research.md](./research.md) - Understanding system design decisions
|
||||||
|
2. **Explore Data Model**: [data-model.md](./data-model.md) - Entity schemas and relationships
|
||||||
|
3. **API Reference**: [contracts/openapi.yaml](./contracts/openapi.yaml) - Complete endpoint documentation
|
||||||
|
4. **SDK Integration**: [../../SDK_INTEGRATION_LESSONS.md](../../SDK_INTEGRATION_LESSONS.md) - Deep dive into SDK usage
|
||||||
|
5. **Join Development**: [CONTRIBUTING.md](../../CONTRIBUTING.md) - Contributing guidelines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- **Issues**: https://github.com/your-org/geutebruck-api/issues
|
||||||
|
- **Documentation**: https://docs.geutebruck-api.example.com
|
||||||
|
- **GeViScope SDK**: See `C:\GEVISOFT\Documentation\`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version**: 1.0.0
|
||||||
|
**Last Updated**: 2025-12-08
|
||||||
1024
specs/001-surveillance-api/research.md
Normal file
1024
specs/001-surveillance-api/research.md
Normal file
File diff suppressed because it is too large
Load Diff
714
specs/001-surveillance-api/tasks.md
Normal file
714
specs/001-surveillance-api/tasks.md
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
# Tasks: Geutebruck Surveillance API
|
||||||
|
|
||||||
|
**Input**: Design documents from `/specs/001-surveillance-api/`
|
||||||
|
**Prerequisites**: plan.md ✅, spec.md ✅, research.md ✅, data-model.md ✅, contracts/openapi.yaml ✅
|
||||||
|
|
||||||
|
**Tests**: TDD approach enforced - all tests MUST be written first and FAIL before implementation begins.
|
||||||
|
|
||||||
|
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Format: `[ID] [P?] [Story] Description`
|
||||||
|
|
||||||
|
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||||
|
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
|
||||||
|
- Include exact file paths in descriptions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Path Conventions
|
||||||
|
|
||||||
|
This project uses **web application structure**:
|
||||||
|
- **Python API**: `src/api/` (FastAPI application)
|
||||||
|
- **C# SDK Bridge**: `src/sdk-bridge/` (gRPC service)
|
||||||
|
- **Tests**: `tests/api/` (Python), `tests/sdk-bridge/` (C#)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Setup (Shared Infrastructure)
|
||||||
|
|
||||||
|
**Purpose**: Project initialization and basic structure
|
||||||
|
|
||||||
|
- [ ] T001 Create Python project structure: src/api/ with subdirs (models/, schemas/, routers/, services/, clients/, middleware/, websocket/, utils/, migrations/)
|
||||||
|
- [ ] T002 Create C# SDK Bridge structure: src/sdk-bridge/ with GeViScopeBridge.sln, Services/, SDK/, Protos/
|
||||||
|
- [ ] T003 Create test structure: tests/api/ (unit/, integration/, contract/) and tests/sdk-bridge/ (Unit/, Integration/)
|
||||||
|
- [ ] T004 [P] Initialize Python dependencies in requirements.txt (FastAPI, Uvicorn, SQLAlchemy, Redis, grpcio, PyJWT, pytest)
|
||||||
|
- [ ] T005 [P] Initialize C# project with .NET 8.0 gRPC and .NET Framework 4.8 SDK dependencies
|
||||||
|
- [ ] T006 [P] Configure Python linting/formatting (ruff, black, mypy) in pyproject.toml
|
||||||
|
- [ ] T007 [P] Create .env.example with all required environment variables
|
||||||
|
- [ ] T008 [P] Create scripts/setup_dev_environment.ps1 for automated development environment setup
|
||||||
|
- [ ] T009 [P] Create scripts/start_services.ps1 to start Redis, SDK Bridge, and API
|
||||||
|
- [ ] T010 [P] Setup Alembic for database migrations in src/api/migrations/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Foundational (Blocking Prerequisites)
|
||||||
|
|
||||||
|
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
||||||
|
|
||||||
|
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
||||||
|
|
||||||
|
### C# SDK Bridge Foundation
|
||||||
|
|
||||||
|
- [ ] T011 Define gRPC protocol buffer for common types in src/sdk-bridge/Protos/common.proto (Status, Error, Timestamp)
|
||||||
|
- [ ] T012 Create GeViDatabaseWrapper.cs in src/sdk-bridge/SDK/ (wraps GeViDatabase connection lifecycle)
|
||||||
|
- [ ] T013 Implement connection management: Create → RegisterCallback → Connect pattern with retry logic
|
||||||
|
- [ ] T014 [P] Create StateQueryHandler.cs for GetFirst/GetNext enumeration pattern
|
||||||
|
- [ ] T015 [P] Create DatabaseQueryHandler.cs for historical query sessions
|
||||||
|
- [ ] T016 Implement error translation from Windows error codes to gRPC status codes in src/sdk-bridge/Utils/ErrorTranslator.cs
|
||||||
|
- [ ] T017 Setup gRPC server in src/sdk-bridge/Program.cs with service registration
|
||||||
|
|
||||||
|
### Python API Foundation
|
||||||
|
|
||||||
|
- [ ] T018 Create FastAPI app initialization in src/api/main.py with CORS, middleware registration
|
||||||
|
- [ ] T019 [P] Create configuration management in src/api/config.py loading from environment variables
|
||||||
|
- [ ] T020 [P] Setup PostgreSQL connection with SQLAlchemy in src/api/models/__init__.py
|
||||||
|
- [ ] T021 [P] Setup Redis client with connection pooling in src/api/clients/redis_client.py
|
||||||
|
- [ ] T022 Create gRPC SDK Bridge client in src/api/clients/sdk_bridge_client.py with connection pooling
|
||||||
|
- [ ] T023 [P] Implement JWT utilities in src/api/utils/jwt_utils.py (encode, decode, verify)
|
||||||
|
- [ ] T024 [P] Create error translation utilities in src/api/utils/error_translation.py (SDK errors → HTTP status)
|
||||||
|
- [ ] T025 Implement global error handler middleware in src/api/middleware/error_handler.py
|
||||||
|
- [ ] T026 [P] Create base Pydantic schemas in src/api/schemas/__init__.py (ErrorResponse, SuccessResponse)
|
||||||
|
|
||||||
|
### Database & Testing Infrastructure
|
||||||
|
|
||||||
|
- [ ] T027 Create initial Alembic migration for database schema (users, audit_logs tables)
|
||||||
|
- [ ] T028 [P] Setup pytest configuration in tests/api/conftest.py with fixtures (test_db, test_redis, test_client)
|
||||||
|
- [ ] T029 [P] Setup xUnit test infrastructure in tests/sdk-bridge/ with test SDK connection
|
||||||
|
|
||||||
|
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: User Story 1 - Secure API Access (Priority: P1) 🎯 MVP
|
||||||
|
|
||||||
|
**Goal**: Implement JWT-based authentication with role-based access control (viewer, operator, administrator)
|
||||||
|
|
||||||
|
**Independent Test**: Can authenticate with valid credentials to receive JWT token, access protected endpoints with token, and receive 401 for invalid/expired tokens
|
||||||
|
|
||||||
|
### Tests for User Story 1 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T030 [P] [US1] Write contract test for POST /api/v1/auth/login in tests/api/contract/test_auth_contract.py (should FAIL)
|
||||||
|
- [ ] T031 [P] [US1] Write contract test for POST /api/v1/auth/refresh in tests/api/contract/test_auth_contract.py (should FAIL)
|
||||||
|
- [ ] T032 [P] [US1] Write contract test for POST /api/v1/auth/logout in tests/api/contract/test_auth_contract.py (should FAIL)
|
||||||
|
- [ ] T033 [P] [US1] Write integration test for authentication flow in tests/api/integration/test_auth_flow.py (should FAIL)
|
||||||
|
- [ ] T034 [P] [US1] Write unit test for AuthService in tests/api/unit/test_auth_service.py (should FAIL)
|
||||||
|
|
||||||
|
### Implementation for User Story 1
|
||||||
|
|
||||||
|
- [ ] T035 [P] [US1] Create User model in src/api/models/user.py (id, username, password_hash, role, permissions, created_at, updated_at)
|
||||||
|
- [ ] T036 [P] [US1] Create AuditLog model in src/api/models/audit_log.py (id, user_id, action, target, outcome, timestamp, details)
|
||||||
|
- [ ] T037 [US1] Create Alembic migration for User and AuditLog tables
|
||||||
|
- [ ] T038 [P] [US1] Create auth request/response schemas in src/api/schemas/auth.py (LoginRequest, TokenResponse, RefreshRequest)
|
||||||
|
- [ ] T039 [US1] Implement AuthService in src/api/services/auth_service.py (login, refresh, logout, validate_token)
|
||||||
|
- [ ] T040 [US1] Implement password hashing with bcrypt in AuthService
|
||||||
|
- [ ] T041 [US1] Implement JWT token generation (access: 1hr, refresh: 7 days) with Redis session storage
|
||||||
|
- [ ] T042 [US1] Implement authentication middleware in src/api/middleware/auth_middleware.py (verify JWT, extract user)
|
||||||
|
- [ ] T043 [US1] Implement rate limiting middleware for auth endpoints in src/api/middleware/rate_limiter.py (5 attempts/min)
|
||||||
|
- [ ] T044 [US1] Create auth router in src/api/routers/auth.py with login, refresh, logout endpoints
|
||||||
|
- [ ] T045 [US1] Implement audit logging for authentication attempts (success and failures)
|
||||||
|
- [ ] T046 [US1] Add role-based permission checking utilities in src/api/utils/permissions.py
|
||||||
|
|
||||||
|
**Verify**: Run tests T030-T034 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Authentication system complete - can login, get tokens, access protected endpoints
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: User Story 2 - Live Video Stream Access (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Enable users to view live video streams from surveillance cameras with <2s initialization time
|
||||||
|
|
||||||
|
**Independent Test**: Authenticate, request stream URL for camera, receive RTSP URL with token, play stream in video player
|
||||||
|
|
||||||
|
### gRPC Protocol Definitions
|
||||||
|
|
||||||
|
- [ ] T047 [US2] Define camera.proto in src/sdk-bridge/Protos/ (ListCamerasRequest/Response, GetCameraRequest/Response, CameraInfo)
|
||||||
|
- [ ] T048 [US2] Define stream.proto in src/sdk-bridge/Protos/ (StartStreamRequest/Response, StopStreamRequest/Response, StreamInfo)
|
||||||
|
|
||||||
|
### Tests for User Story 2 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T049 [P] [US2] Write contract test for GET /api/v1/cameras in tests/api/contract/test_cameras_contract.py (should FAIL)
|
||||||
|
- [ ] T050 [P] [US2] Write contract test for GET /api/v1/cameras/{id} in tests/api/contract/test_cameras_contract.py (should FAIL)
|
||||||
|
- [ ] T051 [P] [US2] Write contract test for POST /api/v1/cameras/{id}/stream in tests/api/contract/test_cameras_contract.py (should FAIL)
|
||||||
|
- [ ] T052 [P] [US2] Write contract test for DELETE /api/v1/cameras/{id}/stream/{stream_id} in tests/api/contract/test_cameras_contract.py (should FAIL)
|
||||||
|
- [ ] T053 [P] [US2] Write integration test for stream lifecycle in tests/api/integration/test_stream_lifecycle.py (should FAIL)
|
||||||
|
- [ ] T054 [P] [US2] Write unit test for CameraService in tests/api/unit/test_camera_service.py (should FAIL)
|
||||||
|
- [ ] T055 [P] [US2] Write C# unit test for CameraService gRPC in tests/sdk-bridge/Unit/CameraServiceTests.cs (should FAIL)
|
||||||
|
|
||||||
|
### Implementation - SDK Bridge (C#)
|
||||||
|
|
||||||
|
- [ ] T056 [US2] Implement CameraService.cs in src/sdk-bridge/Services/ with ListCameras (GetFirstVideoInput/GetNextVideoInput pattern)
|
||||||
|
- [ ] T057 [US2] Implement GetCameraDetails in CameraService.cs (query video input info: channel, name, capabilities)
|
||||||
|
- [ ] T058 [US2] Implement GetCameraStatus in CameraService.cs (online/offline detection)
|
||||||
|
- [ ] T059 [US2] Implement StreamService.cs in src/sdk-bridge/Services/ with StartStream method
|
||||||
|
- [ ] T060 [US2] Generate RTSP URL with token in StreamService.cs (format: rtsp://host:port/stream/{id}?token={jwt})
|
||||||
|
- [ ] T061 [US2] Implement StopStream method in StreamService.cs
|
||||||
|
- [ ] T062 [US2] Track active streams with channel mapping in StreamService.cs
|
||||||
|
|
||||||
|
### Implementation - Python API
|
||||||
|
|
||||||
|
- [ ] T063 [P] [US2] Create Camera model in src/api/models/camera.py (id, channel, name, description, status, capabilities)
|
||||||
|
- [ ] T064 [P] [US2] Create Stream model in src/api/models/stream.py (id, camera_id, user_id, url, started_at, expires_at)
|
||||||
|
- [ ] T065 [US2] Create Alembic migration for Camera and Stream tables
|
||||||
|
- [ ] T066 [P] [US2] Create camera schemas in src/api/schemas/camera.py (CameraInfo, CameraList, CameraCapabilities)
|
||||||
|
- [ ] T067 [P] [US2] Create stream schemas in src/api/schemas/stream.py (StartStreamRequest, StreamResponse)
|
||||||
|
- [ ] T068 [US2] Implement CameraService in src/api/services/camera_service.py (list, get_details, sync from SDK bridge)
|
||||||
|
- [ ] T069 [US2] Implement StreamService in src/api/services/stream_service.py (start, stop, track active streams)
|
||||||
|
- [ ] T070 [US2] Implement token generation for stream URLs (15min expiration)
|
||||||
|
- [ ] T071 [US2] Create cameras router in src/api/routers/cameras.py with GET /cameras, GET /cameras/{id}
|
||||||
|
- [ ] T072 [US2] Implement stream endpoints: POST /cameras/{id}/stream, DELETE /cameras/{id}/stream/{stream_id}
|
||||||
|
- [ ] T073 [US2] Add permission checks: users can only access cameras they have permission for (403 if unauthorized)
|
||||||
|
- [ ] T074 [US2] Implement camera offline error handling (clear error message when camera unavailable)
|
||||||
|
|
||||||
|
**Verify**: Run tests T049-T055 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Live streaming functional - can list cameras, start/stop streams, play video
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Story 3 - Camera PTZ Control (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Enable remote pan-tilt-zoom control for PTZ-capable cameras with <500ms response time
|
||||||
|
|
||||||
|
**Independent Test**: Send PTZ command (pan left/right, tilt up/down, zoom in/out) to PTZ camera, verify movement occurs
|
||||||
|
|
||||||
|
### gRPC Protocol Definitions
|
||||||
|
|
||||||
|
- [ ] T075 [US3] Define ptz.proto in src/sdk-bridge/Protos/ (PTZMoveRequest, PTZPresetRequest, PTZResponse)
|
||||||
|
|
||||||
|
### Tests for User Story 3 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T076 [P] [US3] Write contract test for POST /api/v1/cameras/{id}/ptz in tests/api/contract/test_ptz_contract.py (should FAIL)
|
||||||
|
- [ ] T077 [P] [US3] Write integration test for PTZ control in tests/api/integration/test_ptz_control.py (should FAIL)
|
||||||
|
- [ ] T078 [P] [US3] Write unit test for PTZService in tests/api/unit/test_ptz_service.py (should FAIL)
|
||||||
|
- [ ] T079 [P] [US3] Write C# unit test for PTZService gRPC in tests/sdk-bridge/Unit/PTZServiceTests.cs (should FAIL)
|
||||||
|
|
||||||
|
### Implementation - SDK Bridge (C#)
|
||||||
|
|
||||||
|
- [ ] T080 [US3] Implement PTZService.cs in src/sdk-bridge/Services/ with MoveCamera method (pan, tilt, zoom, speed)
|
||||||
|
- [ ] T081 [US3] Implement SetPreset and GotoPreset methods in PTZService.cs
|
||||||
|
- [ ] T082 [US3] Implement StopMovement method in PTZService.cs
|
||||||
|
- [ ] T083 [US3] Add PTZ command queuing for concurrent control conflict resolution
|
||||||
|
|
||||||
|
### Implementation - Python API
|
||||||
|
|
||||||
|
- [ ] T084 [P] [US3] Create PTZ schemas in src/api/schemas/ptz.py (PTZMoveCommand, PTZPresetCommand, PTZResponse)
|
||||||
|
- [ ] T085 [US3] Implement PTZService in src/api/services/ptz_service.py (move, set_preset, goto_preset, stop)
|
||||||
|
- [ ] T086 [US3] Add PTZ endpoints to cameras router: POST /cameras/{id}/ptz
|
||||||
|
- [ ] T087 [US3] Implement PTZ capability validation (return error if camera doesn't support PTZ)
|
||||||
|
- [ ] T088 [US3] Implement operator role requirement for PTZ control (viewers can't control PTZ)
|
||||||
|
- [ ] T089 [US3] Add audit logging for all PTZ commands
|
||||||
|
|
||||||
|
**Verify**: Run tests T076-T079 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: PTZ control functional - can move cameras, use presets, operators have control
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: User Story 4 - Real-time Event Notifications (Priority: P1)
|
||||||
|
|
||||||
|
**Goal**: Deliver real-time surveillance event notifications via WebSocket with <100ms latency to 1000+ concurrent clients
|
||||||
|
|
||||||
|
**Independent Test**: Connect to WebSocket, subscribe to event types, trigger test alarm, receive notification within 100ms
|
||||||
|
|
||||||
|
### gRPC Protocol Definitions
|
||||||
|
|
||||||
|
- [ ] T090 [US4] Define event.proto in src/sdk-bridge/Protos/ (SubscribeEventsRequest, EventNotification with server streaming)
|
||||||
|
|
||||||
|
### Tests for User Story 4 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T091 [P] [US4] Write contract test for WebSocket /api/v1/events/stream in tests/api/contract/test_events_contract.py (should FAIL)
|
||||||
|
- [ ] T092 [P] [US4] Write contract test for GET /api/v1/events in tests/api/contract/test_events_contract.py (should FAIL)
|
||||||
|
- [ ] T093 [P] [US4] Write integration test for event notification flow in tests/api/integration/test_event_notifications.py (should FAIL)
|
||||||
|
- [ ] T094 [P] [US4] Write unit test for EventService in tests/api/unit/test_event_service.py (should FAIL)
|
||||||
|
- [ ] T095 [P] [US4] Write C# unit test for EventService gRPC in tests/sdk-bridge/Unit/EventServiceTests.cs (should FAIL)
|
||||||
|
|
||||||
|
### Implementation - SDK Bridge (C#)
|
||||||
|
|
||||||
|
- [ ] T096 [US4] Implement EventService.cs in src/sdk-bridge/Services/ with SubscribeEvents (server streaming)
|
||||||
|
- [ ] T097 [US4] Register SDK event callbacks for motion, alarms, analytics, system events
|
||||||
|
- [ ] T098 [US4] Map SDK events to gRPC EventNotification messages
|
||||||
|
- [ ] T099 [US4] Implement event filtering by type and camera channel
|
||||||
|
|
||||||
|
### Implementation - Python API
|
||||||
|
|
||||||
|
- [ ] T100 [P] [US4] Create Event model in src/api/models/event.py (id, type, camera_id, timestamp, severity, data)
|
||||||
|
- [ ] T101 [US4] Create Alembic migration for Event table
|
||||||
|
- [ ] T102 [P] [US4] Create event schemas in src/api/schemas/event.py (EventNotification, EventQuery, EventFilter)
|
||||||
|
- [ ] T103 [US4] Implement WebSocket connection manager in src/api/websocket/connection_manager.py (add, remove, broadcast)
|
||||||
|
- [ ] T104 [US4] Implement Redis pub/sub event broadcaster in src/api/websocket/event_broadcaster.py (subscribe to SDK bridge events)
|
||||||
|
- [ ] T105 [US4] Create background task to consume SDK bridge event stream and publish to Redis
|
||||||
|
- [ ] T106 [US4] Implement WebSocket endpoint in src/api/routers/events.py: WS /events/stream
|
||||||
|
- [ ] T107 [US4] Implement event subscription management (subscribe, unsubscribe to event types)
|
||||||
|
- [ ] T108 [US4] Implement client reconnection handling with missed event recovery
|
||||||
|
- [ ] T109 [US4] Implement EventService in src/api/services/event_service.py (query historical events)
|
||||||
|
- [ ] T110 [US4] Create REST endpoint: GET /events (query with filters: camera, type, time range)
|
||||||
|
- [ ] T111 [US4] Implement permission filtering (users only receive events for authorized cameras)
|
||||||
|
|
||||||
|
**Verify**: Run tests T091-T095 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Event notifications working - WebSocket delivers real-time alerts, query historical events
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: User Story 5 - Recording Management (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Manage video recording settings and query recorded footage for investigations
|
||||||
|
|
||||||
|
**Independent Test**: Start recording on camera, query recordings by time range, receive list with download URLs
|
||||||
|
|
||||||
|
### gRPC Protocol Definitions
|
||||||
|
|
||||||
|
- [ ] T112 [US5] Define recording.proto in src/sdk-bridge/Protos/ (QueryRecordingsRequest, StartRecordingRequest, RecordingInfo)
|
||||||
|
|
||||||
|
### Tests for User Story 5 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T113 [P] [US5] Write contract test for GET /api/v1/recordings in tests/api/contract/test_recordings_contract.py (should FAIL)
|
||||||
|
- [ ] T114 [P] [US5] Write contract test for POST /api/v1/recordings/{id}/export in tests/api/contract/test_recordings_contract.py (should FAIL)
|
||||||
|
- [ ] T115 [P] [US5] Write integration test for recording management in tests/api/integration/test_recording_management.py (should FAIL)
|
||||||
|
- [ ] T116 [P] [US5] Write unit test for RecordingService in tests/api/unit/test_recording_service.py (should FAIL)
|
||||||
|
- [ ] T117 [P] [US5] Write C# unit test for RecordingService gRPC in tests/sdk-bridge/Unit/RecordingServiceTests.cs (should FAIL)
|
||||||
|
|
||||||
|
### Implementation - SDK Bridge (C#)
|
||||||
|
|
||||||
|
- [ ] T118 [US5] Implement RecordingService.cs in src/sdk-bridge/Services/ with QueryRecordings (database query with time range)
|
||||||
|
- [ ] T119 [US5] Implement StartRecording and StopRecording methods
|
||||||
|
- [ ] T120 [US5] Implement GetRecordingCapacity method (ring buffer metrics)
|
||||||
|
- [ ] T121 [US5] Query recording segments using CDBQCreateActionQuery pattern
|
||||||
|
|
||||||
|
### Implementation - Python API
|
||||||
|
|
||||||
|
- [ ] T122 [P] [US5] Create Recording model in src/api/models/recording.py (id, camera_id, start_time, end_time, size_bytes, trigger_type)
|
||||||
|
- [ ] T123 [US5] Create Alembic migration for Recording table
|
||||||
|
- [ ] T124 [P] [US5] Create recording schemas in src/api/schemas/recording.py (RecordingQuery, RecordingInfo, ExportRequest)
|
||||||
|
- [ ] T125 [US5] Implement RecordingService in src/api/services/recording_service.py (query, start, stop, export)
|
||||||
|
- [ ] T126 [US5] Create recordings router in src/api/routers/recordings.py: GET /recordings, POST /recordings/{id}/export
|
||||||
|
- [ ] T127 [US5] Implement recording query with filters (camera, time range, event type)
|
||||||
|
- [ ] T128 [US5] Implement export job creation (async job with progress tracking)
|
||||||
|
- [ ] T129 [US5] Implement ring buffer capacity monitoring and warnings (alert at 90%)
|
||||||
|
- [ ] T130 [US5] Add administrator role requirement for starting/stopping recording
|
||||||
|
|
||||||
|
**Verify**: Run tests T113-T117 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Recording management functional - query, export, capacity monitoring
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: User Story 6 - Video Analytics Configuration (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: Configure video content analysis features (VMD, object tracking, perimeter protection)
|
||||||
|
|
||||||
|
**Independent Test**: Configure motion detection zone on camera, trigger motion, verify analytics event generated
|
||||||
|
|
||||||
|
### gRPC Protocol Definitions
|
||||||
|
|
||||||
|
- [ ] T131 [US6] Define analytics.proto in src/sdk-bridge/Protos/ (ConfigureAnalyticsRequest, AnalyticsConfig with union types for VMD/NPR/OBTRACK/G-Tect)
|
||||||
|
|
||||||
|
### Tests for User Story 6 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T132 [P] [US6] Write contract test for GET /api/v1/analytics/{camera_id} in tests/api/contract/test_analytics_contract.py (should FAIL)
|
||||||
|
- [ ] T133 [P] [US6] Write contract test for POST /api/v1/analytics/{camera_id} in tests/api/contract/test_analytics_contract.py (should FAIL)
|
||||||
|
- [ ] T134 [P] [US6] Write integration test for analytics configuration in tests/api/integration/test_analytics_config.py (should FAIL)
|
||||||
|
- [ ] T135 [P] [US6] Write unit test for AnalyticsService in tests/api/unit/test_analytics_service.py (should FAIL)
|
||||||
|
- [ ] T136 [P] [US6] Write C# unit test for AnalyticsService gRPC in tests/sdk-bridge/Unit/AnalyticsServiceTests.cs (should FAIL)
|
||||||
|
|
||||||
|
### Implementation - SDK Bridge (C#)
|
||||||
|
|
||||||
|
- [ ] T137 [US6] Implement AnalyticsService.cs in src/sdk-bridge/Services/ with ConfigureAnalytics method
|
||||||
|
- [ ] T138 [US6] Implement GetAnalyticsConfig method (query current analytics settings)
|
||||||
|
- [ ] T139 [US6] Map analytics types to SDK sensor types (VMD, NPR, OBTRACK, G-Tect, CPA)
|
||||||
|
- [ ] T140 [US6] Implement region/zone configuration for analytics
|
||||||
|
|
||||||
|
### Implementation - Python API
|
||||||
|
|
||||||
|
- [ ] T141 [P] [US6] Create AnalyticsConfig model in src/api/models/analytics_config.py (id, camera_id, type, enabled, configuration JSON)
|
||||||
|
- [ ] T142 [US6] Create Alembic migration for AnalyticsConfig table
|
||||||
|
- [ ] T143 [P] [US6] Create analytics schemas in src/api/schemas/analytics.py (AnalyticsConfigRequest, VMDConfig, NPRConfig, OBTRACKConfig)
|
||||||
|
- [ ] T144 [US6] Implement AnalyticsService in src/api/services/analytics_service.py (configure, get_config, validate)
|
||||||
|
- [ ] T145 [US6] Create analytics router in src/api/routers/analytics.py: GET/POST /analytics/{camera_id}
|
||||||
|
- [ ] T146 [US6] Implement analytics capability validation (return error if camera doesn't support requested analytics)
|
||||||
|
- [ ] T147 [US6] Add administrator role requirement for analytics configuration
|
||||||
|
- [ ] T148 [US6] Implement schedule support for analytics (enable/disable by time/day)
|
||||||
|
|
||||||
|
**Verify**: Run tests T132-T136 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Analytics configuration functional - configure VMD, NPR, OBTRACK, receive analytics events
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9: User Story 7 - Multi-Camera Management (Priority: P2)
|
||||||
|
|
||||||
|
**Goal**: View and manage multiple cameras simultaneously with location grouping
|
||||||
|
|
||||||
|
**Independent Test**: Request camera list, verify all authorized cameras returned with metadata, group by location
|
||||||
|
|
||||||
|
### Tests for User Story 7 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T149 [P] [US7] Write contract test for camera list with filtering/pagination in tests/api/contract/test_camera_list_contract.py (should FAIL)
|
||||||
|
- [ ] T150 [P] [US7] Write integration test for multi-camera operations in tests/api/integration/test_multi_camera.py (should FAIL)
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
- [ ] T151 [P] [US7] Add location field to Camera model (update migration)
|
||||||
|
- [ ] T152 [US7] Implement camera list filtering by location, status, capabilities in CameraService
|
||||||
|
- [ ] T153 [US7] Implement pagination for camera list (page, page_size parameters)
|
||||||
|
- [ ] T154 [US7] Update GET /cameras endpoint with query parameters (location, status, capabilities, page, page_size)
|
||||||
|
- [ ] T155 [US7] Implement camera grouping by location in response
|
||||||
|
- [ ] T156 [US7] Implement concurrent stream limit tracking (warn if approaching limit)
|
||||||
|
- [ ] T157 [US7] Add camera status change notifications via WebSocket (camera goes offline → event)
|
||||||
|
|
||||||
|
**Verify**: Run tests T149-T150 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Multi-camera management functional - filtering, grouping, concurrent access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 10: User Story 8 - License Plate Recognition Integration (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Receive automatic license plate recognition events with watchlist matching
|
||||||
|
|
||||||
|
**Independent Test**: Configure NPR zone, drive test vehicle through zone, receive NPR event with plate number
|
||||||
|
|
||||||
|
### Tests for User Story 8 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T158 [P] [US8] Write integration test for NPR events in tests/api/integration/test_npr_events.py (should FAIL)
|
||||||
|
- [ ] T159 [P] [US8] Write unit test for NPR watchlist matching in tests/api/unit/test_npr_service.py (should FAIL)
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
- [ ] T160 [P] [US8] Create NPREvent model extending Event in src/api/models/event.py (plate_number, country_code, confidence, image_url)
|
||||||
|
- [ ] T161 [US8] Create Alembic migration for NPREvent table
|
||||||
|
- [ ] T162 [P] [US8] Create Watchlist model in src/api/models/watchlist.py (id, plate_number, alert_level, notes)
|
||||||
|
- [ ] T163 [US8] Create Alembic migration for Watchlist table
|
||||||
|
- [ ] T164 [P] [US8] Create NPR schemas in src/api/schemas/npr.py (NPREventData, WatchlistEntry)
|
||||||
|
- [ ] T165 [US8] Implement NPR event handling in EventService (parse NPR data from SDK)
|
||||||
|
- [ ] T166 [US8] Implement watchlist matching service (check incoming plates against watchlist)
|
||||||
|
- [ ] T167 [US8] Implement high-priority alerts for watchlist matches
|
||||||
|
- [ ] T168 [US8] Add NPR-specific filtering to GET /events endpoint
|
||||||
|
- [ ] T169 [US8] Create watchlist management endpoints: GET/POST/DELETE /api/v1/watchlist
|
||||||
|
|
||||||
|
**Verify**: Run tests T158-T159 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: NPR integration functional - receive plate events, watchlist matching, alerts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 11: User Story 9 - Video Export and Backup (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Export specific video segments for evidence with progress tracking
|
||||||
|
|
||||||
|
**Independent Test**: Request export of 10-minute segment, poll job status, download exported file
|
||||||
|
|
||||||
|
### Tests for User Story 9 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T170 [P] [US9] Write contract test for export job in tests/api/contract/test_export_contract.py (should FAIL)
|
||||||
|
- [ ] T171 [P] [US9] Write integration test for export workflow in tests/api/integration/test_export_workflow.py (should FAIL)
|
||||||
|
- [ ] T172 [P] [US9] Write unit test for ExportService in tests/api/unit/test_export_service.py (should FAIL)
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
- [ ] T173 [P] [US9] Create ExportJob model in src/api/models/export_job.py (id, camera_id, start_time, end_time, status, progress, file_path)
|
||||||
|
- [ ] T174 [US9] Create Alembic migration for ExportJob table
|
||||||
|
- [ ] T175 [P] [US9] Create export schemas in src/api/schemas/export.py (ExportRequest, ExportJobStatus)
|
||||||
|
- [ ] T176 [US9] Implement ExportService in src/api/services/export_service.py (create_job, get_status, download)
|
||||||
|
- [ ] T177 [US9] Implement background worker for export processing (query recordings, concatenate, encode to MP4)
|
||||||
|
- [ ] T178 [US9] Implement progress tracking and updates (percentage complete, ETA)
|
||||||
|
- [ ] T179 [US9] Update POST /recordings/{id}/export to create export job and return job ID
|
||||||
|
- [ ] T180 [US9] Create GET /api/v1/exports/{job_id} endpoint for job status polling
|
||||||
|
- [ ] T181 [US9] Create GET /api/v1/exports/{job_id}/download endpoint for file download
|
||||||
|
- [ ] T182 [US9] Implement cleanup of old export files (auto-delete after 24 hours)
|
||||||
|
- [ ] T183 [US9] Add timestamp watermarking to exported video
|
||||||
|
|
||||||
|
**Verify**: Run tests T170-T172 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Video export functional - create jobs, track progress, download files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 12: User Story 10 - System Health Monitoring (Priority: P3)
|
||||||
|
|
||||||
|
**Goal**: Monitor API and surveillance system health with proactive alerts
|
||||||
|
|
||||||
|
**Independent Test**: Query health endpoint, verify SDK connectivity status, simulate component failure
|
||||||
|
|
||||||
|
### Tests for User Story 10 (TDD - Write FIRST, Ensure FAIL)
|
||||||
|
|
||||||
|
- [ ] T184 [P] [US10] Write contract test for GET /api/v1/health in tests/api/contract/test_health_contract.py (should FAIL)
|
||||||
|
- [ ] T185 [P] [US10] Write contract test for GET /api/v1/status in tests/api/contract/test_health_contract.py (should FAIL)
|
||||||
|
- [ ] T186 [P] [US10] Write integration test for health monitoring in tests/api/integration/test_health_monitoring.py (should FAIL)
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
- [ ] T187 [P] [US10] Create health schemas in src/api/schemas/health.py (HealthResponse, SystemStatus, ComponentHealth)
|
||||||
|
- [ ] T188 [US10] Implement HealthService in src/api/services/health_service.py (check all components)
|
||||||
|
- [ ] T189 [US10] Implement SDK Bridge health check (gRPC connectivity test)
|
||||||
|
- [ ] T190 [US10] Implement Redis health check (ping test)
|
||||||
|
- [ ] T191 [US10] Implement PostgreSQL health check (simple query)
|
||||||
|
- [ ] T192 [US10] Implement disk space check for recordings (warn if <10%)
|
||||||
|
- [ ] T193 [US10] Create system router in src/api/routers/system.py: GET /health, GET /status
|
||||||
|
- [ ] T194 [US10] Implement GET /health endpoint (public, returns basic status)
|
||||||
|
- [ ] T195 [US10] Implement GET /status endpoint (authenticated, returns detailed metrics)
|
||||||
|
- [ ] T196 [US10] Add Prometheus metrics endpoint at /metrics (request count, latency, errors, active streams, WebSocket connections)
|
||||||
|
- [ ] T197 [US10] Implement background health monitoring task (check every 30s, alert on failures)
|
||||||
|
|
||||||
|
**Verify**: Run tests T184-T186 - they should now PASS
|
||||||
|
|
||||||
|
**Checkpoint**: Health monitoring functional - status endpoints, metrics, component checks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 13: Polish & Cross-Cutting Concerns
|
||||||
|
|
||||||
|
**Purpose**: Improvements that affect multiple user stories
|
||||||
|
|
||||||
|
- [ ] T198 [P] Add comprehensive API documentation to all endpoints (docstrings, parameter descriptions)
|
||||||
|
- [ ] T199 [P] Create architecture diagram in docs/architecture.md with component interaction flows
|
||||||
|
- [ ] T200 [P] Create SDK integration guide in docs/sdk-integration.md with connection patterns
|
||||||
|
- [ ] T201 [P] Create deployment guide in docs/deployment.md (Windows Server, Docker, environment setup)
|
||||||
|
- [ ] T202 [P] Add OpenAPI specification auto-generation from code annotations
|
||||||
|
- [ ] T203 [P] Implement request/response logging with correlation IDs for debugging
|
||||||
|
- [ ] T204 [P] Add performance profiling endpoints (debug mode only)
|
||||||
|
- [ ] T205 [P] Create load testing scripts for concurrent streams and WebSocket connections
|
||||||
|
- [ ] T206 [P] Implement graceful shutdown handling (close connections, flush logs)
|
||||||
|
- [ ] T207 [P] Add TLS/HTTPS configuration guide and certificate management
|
||||||
|
- [ ] T208 [P] Security hardening: Remove stack traces from production errors, sanitize logs
|
||||||
|
- [ ] T209 [P] Add database connection pooling optimization
|
||||||
|
- [ ] T210 [P] Implement API response caching for camera lists (Redis cache, 60s TTL)
|
||||||
|
- [ ] T211 [P] Create GitHub Actions CI/CD pipeline (run tests, build Docker images)
|
||||||
|
- [ ] T212 [P] Add code coverage reporting (target 80% minimum)
|
||||||
|
- [ ] T213 Validate quickstart.md by following guide end-to-end
|
||||||
|
- [ ] T214 Create README.md with project overview, links to documentation
|
||||||
|
- [ ] T215 Final security audit: Check for OWASP top 10 vulnerabilities
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & Execution Order
|
||||||
|
|
||||||
|
### Phase Dependencies
|
||||||
|
|
||||||
|
- **Setup (Phase 1)**: No dependencies - can start immediately
|
||||||
|
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
||||||
|
- **User Stories (Phase 3-12)**: All depend on Foundational phase completion
|
||||||
|
- User Story 1 (P1): Authentication - NO dependencies on other stories
|
||||||
|
- User Story 2 (P1): Live Streaming - Requires User Story 1 (auth for protected endpoints)
|
||||||
|
- User Story 3 (P1): PTZ Control - Requires User Story 1 (auth) and User Story 2 (camera service exists)
|
||||||
|
- User Story 4 (P1): Event Notifications - Requires User Story 1 (auth), User Story 2 (camera service)
|
||||||
|
- User Story 5 (P2): Recording Management - Requires User Story 1 (auth), User Story 2 (camera service)
|
||||||
|
- User Story 6 (P2): Analytics Config - Requires User Story 1 (auth), User Story 2 (camera service), User Story 4 (events)
|
||||||
|
- User Story 7 (P2): Multi-Camera - Extends User Story 2 (camera service)
|
||||||
|
- User Story 8 (P3): NPR Integration - Requires User Story 4 (events), User Story 6 (analytics)
|
||||||
|
- User Story 9 (P3): Video Export - Requires User Story 5 (recording management)
|
||||||
|
- User Story 10 (P3): Health Monitoring - Can start after Foundational, but best after all services exist
|
||||||
|
- **Polish (Phase 13)**: Depends on all desired user stories being complete
|
||||||
|
|
||||||
|
### Critical Path (Sequential)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Setup
|
||||||
|
↓
|
||||||
|
Phase 2: Foundational (BLOCKS all user stories)
|
||||||
|
↓
|
||||||
|
Phase 3: User Story 1 - Authentication (BLOCKS all protected endpoints)
|
||||||
|
↓
|
||||||
|
Phase 4: User Story 2 - Live Streaming (BLOCKS camera-dependent features)
|
||||||
|
↓
|
||||||
|
Phase 5: User Story 3 - PTZ Control
|
||||||
|
↓
|
||||||
|
Phase 6: User Story 4 - Event Notifications (BLOCKS analytics)
|
||||||
|
↓
|
||||||
|
[Phase 7-12 can proceed in parallel after their dependencies are met]
|
||||||
|
↓
|
||||||
|
Phase 13: Polish
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Story Dependencies
|
||||||
|
|
||||||
|
- **US1 (Authentication)**: No dependencies - can start after Foundational
|
||||||
|
- **US2 (Live Streaming)**: Depends on US1 completion
|
||||||
|
- **US3 (PTZ Control)**: Depends on US1, US2 completion
|
||||||
|
- **US4 (Event Notifications)**: Depends on US1, US2 completion
|
||||||
|
- **US5 (Recording Management)**: Depends on US1, US2 completion
|
||||||
|
- **US6 (Analytics Config)**: Depends on US1, US2, US4 completion
|
||||||
|
- **US7 (Multi-Camera)**: Depends on US2 completion
|
||||||
|
- **US8 (NPR Integration)**: Depends on US4, US6 completion
|
||||||
|
- **US9 (Video Export)**: Depends on US5 completion
|
||||||
|
- **US10 (Health Monitoring)**: Can start after Foundational
|
||||||
|
|
||||||
|
### Parallel Opportunities
|
||||||
|
|
||||||
|
**Within Phases**:
|
||||||
|
- Phase 1 (Setup): T004-T010 can run in parallel (all marked [P])
|
||||||
|
- Phase 2 (Foundational): T014-T015, T019-T021, T023-T024, T028-T029 can run in parallel
|
||||||
|
|
||||||
|
**Within User Stories**:
|
||||||
|
- US1 Tests: T030-T034 can run in parallel
|
||||||
|
- US1 Models: T035-T036 can run in parallel
|
||||||
|
- US1 Schemas: T038 independent
|
||||||
|
- US2 Tests: T049-T055 can run in parallel
|
||||||
|
- US2 Models: T063-T064 can run in parallel
|
||||||
|
- US2 Schemas: T066-T067 can run in parallel
|
||||||
|
- [Similar pattern for all user stories]
|
||||||
|
|
||||||
|
**Across User Stories** (if team capacity allows):
|
||||||
|
- After Foundational completes: US1 can start
|
||||||
|
- After US1 completes: US2, US5 can start in parallel
|
||||||
|
- After US2 completes: US3, US4, US7 can start in parallel
|
||||||
|
- After US4 completes: US6 can start
|
||||||
|
- After US5 completes: US9 can start
|
||||||
|
- After US6 completes: US8 can start
|
||||||
|
- US10 can start any time after Foundational
|
||||||
|
|
||||||
|
**Polish Phase**: T198-T212, T214-T215 all marked [P] can run in parallel
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Example: User Story 2 (Live Streaming)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Step 1: Write all tests in parallel (TDD - ensure they FAIL)
|
||||||
|
Task T049: Contract test for GET /cameras
|
||||||
|
Task T050: Contract test for GET /cameras/{id}
|
||||||
|
Task T051: Contract test for POST /cameras/{id}/stream
|
||||||
|
Task T052: Contract test for DELETE /cameras/{id}/stream/{stream_id}
|
||||||
|
Task T053: Integration test for stream lifecycle
|
||||||
|
Task T054: Unit test for CameraService (Python)
|
||||||
|
Task T055: Unit test for CameraService (C#)
|
||||||
|
|
||||||
|
# Step 2: Create models in parallel
|
||||||
|
Task T063: Camera model
|
||||||
|
Task T064: Stream model
|
||||||
|
|
||||||
|
# Step 3: Create schemas in parallel
|
||||||
|
Task T066: Camera schemas
|
||||||
|
Task T067: Stream schemas
|
||||||
|
|
||||||
|
# Step 4: Implement services sequentially (dependency on models)
|
||||||
|
Task T068: CameraService (depends on T063, T064)
|
||||||
|
Task T069: StreamService (depends on T068)
|
||||||
|
|
||||||
|
# Step 5: Implement SDK Bridge sequentially
|
||||||
|
Task T056: CameraService.cs (depends on gRPC proto T047)
|
||||||
|
Task T059: StreamService.cs (depends on gRPC proto T048)
|
||||||
|
|
||||||
|
# Step 6: Implement routers sequentially (depends on services)
|
||||||
|
Task T071: Cameras router
|
||||||
|
Task T072: Stream endpoints
|
||||||
|
|
||||||
|
# Verify: Run tests T049-T055 - they should now PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### MVP First (User Stories 1-4 Only)
|
||||||
|
|
||||||
|
**Rationale**: US1-US4 are all P1 and deliver core surveillance functionality
|
||||||
|
|
||||||
|
1. ✅ Complete Phase 1: Setup
|
||||||
|
2. ✅ Complete Phase 2: Foundational (CRITICAL - blocks all stories)
|
||||||
|
3. ✅ Complete Phase 3: User Story 1 (Authentication) - STOP and TEST
|
||||||
|
4. ✅ Complete Phase 4: User Story 2 (Live Streaming) - STOP and TEST
|
||||||
|
5. ✅ Complete Phase 5: User Story 3 (PTZ Control) - STOP and TEST
|
||||||
|
6. ✅ Complete Phase 6: User Story 4 (Event Notifications) - STOP and TEST
|
||||||
|
7. **STOP and VALIDATE**: Test all P1 stories together as integrated MVP
|
||||||
|
8. Deploy/demo MVP
|
||||||
|
|
||||||
|
**MVP Delivers**:
|
||||||
|
- ✅ Secure authentication with RBAC
|
||||||
|
- ✅ Live video streaming from cameras
|
||||||
|
- ✅ PTZ camera control
|
||||||
|
- ✅ Real-time event notifications
|
||||||
|
|
||||||
|
**Not in MVP** (can add incrementally):
|
||||||
|
- Recording management (US5)
|
||||||
|
- Analytics configuration (US6)
|
||||||
|
- Multi-camera enhancements (US7)
|
||||||
|
- NPR integration (US8)
|
||||||
|
- Video export (US9)
|
||||||
|
- Health monitoring (US10)
|
||||||
|
|
||||||
|
### Incremental Delivery (After MVP)
|
||||||
|
|
||||||
|
1. **MVP** (US1-4) → Deploy → Validate
|
||||||
|
2. **+Recording** (US5) → Deploy → Validate
|
||||||
|
3. **+Analytics** (US6) → Deploy → Validate
|
||||||
|
4. **+Multi-Camera** (US7) → Deploy → Validate
|
||||||
|
5. **+NPR** (US8) → Deploy → Validate
|
||||||
|
6. **+Export** (US9) → Deploy → Validate
|
||||||
|
7. **+Health** (US10) → Deploy → Validate
|
||||||
|
8. **+Polish** (Phase 13) → Final Release
|
||||||
|
|
||||||
|
Each increment adds value without breaking previous functionality.
|
||||||
|
|
||||||
|
### Parallel Team Strategy
|
||||||
|
|
||||||
|
With 3 developers after Foundational phase completes:
|
||||||
|
|
||||||
|
**Week 1-2**: All work on US1 together (foundational for everything)
|
||||||
|
|
||||||
|
**Week 3-4**:
|
||||||
|
- Developer A: US2 (Live Streaming)
|
||||||
|
- Developer B: Start US4 (Events - can partially proceed)
|
||||||
|
- Developer C: Setup/tooling improvements
|
||||||
|
|
||||||
|
**Week 5-6**:
|
||||||
|
- Developer A: US3 (PTZ - depends on US2)
|
||||||
|
- Developer B: Complete US4 (Events)
|
||||||
|
- Developer C: US5 (Recording)
|
||||||
|
|
||||||
|
**Week 7+**:
|
||||||
|
- Developer A: US6 (Analytics)
|
||||||
|
- Developer B: US7 (Multi-Camera)
|
||||||
|
- Developer C: US9 (Export)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task Summary
|
||||||
|
|
||||||
|
**Total Tasks**: 215
|
||||||
|
|
||||||
|
**By Phase**:
|
||||||
|
- Phase 1 (Setup): 10 tasks
|
||||||
|
- Phase 2 (Foundational): 19 tasks
|
||||||
|
- Phase 3 (US1 - Authentication): 17 tasks
|
||||||
|
- Phase 4 (US2 - Live Streaming): 29 tasks
|
||||||
|
- Phase 5 (US3 - PTZ Control): 15 tasks
|
||||||
|
- Phase 6 (US4 - Event Notifications): 22 tasks
|
||||||
|
- Phase 7 (US5 - Recording Management): 19 tasks
|
||||||
|
- Phase 8 (US6 - Analytics Config): 18 tasks
|
||||||
|
- Phase 9 (US7 - Multi-Camera): 9 tasks
|
||||||
|
- Phase 10 (US8 - NPR Integration): 12 tasks
|
||||||
|
- Phase 11 (US9 - Video Export): 14 tasks
|
||||||
|
- Phase 12 (US10 - Health Monitoring): 14 tasks
|
||||||
|
- Phase 13 (Polish): 18 tasks
|
||||||
|
|
||||||
|
**MVP Tasks** (Phases 1-6): 112 tasks
|
||||||
|
|
||||||
|
**Tests**: 80+ test tasks (all marked TDD - write first, ensure FAIL)
|
||||||
|
|
||||||
|
**Parallel Tasks**: 100+ tasks marked [P]
|
||||||
|
|
||||||
|
**Estimated Timeline**:
|
||||||
|
- MVP (US1-4): 8-10 weeks (1 developer) or 4-6 weeks (3 developers)
|
||||||
|
- Full Feature Set (US1-10 + Polish): 16-20 weeks (1 developer) or 8-12 weeks (3 developers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **[P] tasks**: Different files, no dependencies - safe to parallelize
|
||||||
|
- **[Story] labels**: Maps task to specific user story for traceability
|
||||||
|
- **TDD enforced**: All test tasks MUST be written first and FAIL before implementation
|
||||||
|
- **Independent stories**: Each user story should be independently completable and testable
|
||||||
|
- **Commit frequently**: After each task or logical group
|
||||||
|
- **Stop at checkpoints**: Validate each story independently before proceeding
|
||||||
|
- **MVP focus**: Complete US1-4 first for deployable surveillance system
|
||||||
|
- **Avoid**: Vague tasks, same-file conflicts, cross-story dependencies that break independence
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Generated**: 2025-12-08
|
||||||
|
**Based on**: spec.md (10 user stories), plan.md (tech stack), data-model.md (7 entities), contracts/openapi.yaml (17 endpoints)
|
||||||
Reference in New Issue
Block a user