Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/make_ipks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Make IPKs

on:
push:
branches:
- "*"
tags-ignore:
- "*"
pull_request:
release:
types: [published]

jobs:

build-system-viewer:
name: Build IPK for SystemViewer
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build IPK (SystemCore)
run: cd systemcore-apps/system-view && chmod +x build.sh && ./build.sh
- name: Upload development artifact (SystemCore)
uses: actions/upload-artifact@v4
with:
name: pv-system-viewer.ipk
path: systemcore-apps/system-view/pv-system-viewer_*.ipk
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,5 @@ photon-server/src/main/resources/web/*
node_modules
dist
components.d.ts

*.ipk
2 changes: 2 additions & 0 deletions systemcore-apps/system-view/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
!overlay/
!overlay/**
93 changes: 93 additions & 0 deletions systemcore-apps/system-view/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/bin/bash

set -e

# Extract package info from control/control file
if [ ! -f "control/control" ]; then
echo "Error: control/control not found!"
echo "Create a control/control file with package metadata"
exit 1
fi

PACKAGE_NAME=$(grep "^Package:" control/control | cut -d' ' -f2- | tr -d ' ')
PACKAGE_VERSION=$(grep "^Version:" control/control | cut -d' ' -f2- | tr -d ' ')

# Validate required fields
if [ -z "$PACKAGE_NAME" ] || [ -z "$PACKAGE_VERSION" ]; then
echo "Err: Package and Version must be set in control/control"
echo "Package: my-package"
echo "Version: 1.0.0"
exit 1
fi

PACKAGE_DIR="${PACKAGE_NAME}_${PACKAGE_VERSION}"
BUILD_DIR="build"

echo "Building IPK package from overlay structure..."
echo "Package: ${PACKAGE_NAME}_${PACKAGE_VERSION}.ipk"

if [ ! -d "overlay" ]; then
echo "overlay/ directory not found"
exit 1
fi

if [ ! -d "control" ]; then
echo "Error: control/ directory not found!"
echo "Create control/ with control, postinst, prerm, postrm files"
exit 1
fi

echo "Cleaning previous build..."
rm -rf "$BUILD_DIR"
mkdir -p "$BUILD_DIR/$PACKAGE_DIR"

echo "Copying overlay structure..."
cp -r overlay/* "$BUILD_DIR/$PACKAGE_DIR/"

echo "Copying CONTROL files..."
mkdir -p "$BUILD_DIR/$PACKAGE_DIR/CONTROL"
cp control/* "$BUILD_DIR/$PACKAGE_DIR/CONTROL/"

echo "Setting file permissions..."

# Make scripts executable
find "$BUILD_DIR/$PACKAGE_DIR" -name "*.py" -exec chmod +x {} \;

if [ -d "$BUILD_DIR/$PACKAGE_DIR/CONTROL" ]; then
chmod +x "$BUILD_DIR/$PACKAGE_DIR/CONTROL"/* 2>/dev/null || true
fi

find "$BUILD_DIR/$PACKAGE_DIR" -name "*.sh" -exec chmod +x {} \;

echo "Building IPK dir structure"
cd "$BUILD_DIR"

echo "Creating data.tar.gz"
tar --exclude='CONTROL' -czf data.tar.gz -C "$PACKAGE_DIR" .

echo "Creating control.tar.gz"
tar -czf control.tar.gz -C "$PACKAGE_DIR/CONTROL" .

echo "Creating IPK..."
ar r "../${PACKAGE_NAME}_${PACKAGE_VERSION}.ipk" control.tar.gz data.tar.gz

cd ..

echo ""
echo "IPK package created."
echo "Package: ${PACKAGE_NAME}_${PACKAGE_VERSION}.ipk"
echo ""
echo "Package structure:"
echo " CONTROL files:"
find control -type f | sort | sed 's/^/ /'
echo " Overlay files (will be installed):"
find overlay -type f | sort | sed 's/^overlay/ /' | head -15

if [ $(find overlay -type f | wc -l) -gt 15 ]; then
echo " ... and $(($(find overlay -type f | wc -l) - 15)) more files"
fi

rm -rf "$BUILD_DIR"

echo ""
echo "Build complete"
13 changes: 13 additions & 0 deletions systemcore-apps/system-view/control/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Package: pv-system-viewer
Version: 0.1.0
Description: PhotonVision System Viewer - A simple interface to see all PhotonVision cameras.
Section: development
Priority: optional
Maintainer: PhotonVision Team
Architecture: all
Source: local
X-Port: 5804
X-Has-UI: true
X-Auto-Start: true
X-Icon-Path: /usr/share/pv-system-viewer.png
X-Display-Name: PhotonVision System Viewer
29 changes: 29 additions & 0 deletions systemcore-apps/system-view/control/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/sh
PACKAGE_NAME="pv-system-viewer"

echo "Configuring service for $PACKAGE_NAME"

systemctl daemon-reload
sleep 1

echo "Enabling service for auto-start on boot..."
systemctl enable $PACKAGE_NAME.service

echo "Starting service..."
if systemctl start $PACKAGE_NAME.service; then
echo "Service started successfully"

sleep 2

if systemctl is-active $PACKAGE_NAME.service >/dev/null 2>&1; then
echo "Service is now running"
else
echo "Service may have an issue"
fi
else
echo "Failed to start service automatically"
fi

echo "Package installation complete"

exit 0
15 changes: 15 additions & 0 deletions systemcore-apps/system-view/control/postrm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh
PACKAGE_NAME="pv-system-viewer"

systemctl daemon-reload
systemctl reset-failed $PACKAGE_NAME.service 2>/dev/null || true

if systemctl list-unit-files | grep -q "$PACKAGE_NAME.service"; then
echo "Service still appears in systemd unit files (normal until next boot)"
else
echo "Service removed from systemd"
fi

echo "Service cleanup complete."

exit 0
27 changes: 27 additions & 0 deletions systemcore-apps/system-view/control/prerm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/sh
PACKAGE_NAME="pv-system-viewer"

echo "Stopping $PACKAGE_NAME service..."

if systemctl is-active $PACKAGE_NAME.service >/dev/null 2>&1; then
echo "Service is running, stopping it..."
if systemctl stop $PACKAGE_NAME.service; then
echo "Service stopped successfully"
else
echo "Service stop may have failed, continuing anyway..."
fi
else
echo "Service was not running"
fi

# Disable the service
echo "Disabling service auto-start..."
if systemctl disable $PACKAGE_NAME.service 2>/dev/null; then
echo "Service auto-start disabled"
else
echo "Service was not enabled or disable failed"
fi

echo "Service stopped and disabled"

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[Unit]
Description=PhotonVision System Viewer - A simple interface to check all connected PhotonVision Coprocessor statuses
After=network.target
Wants=network.target

[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/local/bin/pv-system-viewer/pv-system-viewer.py
Restart=always
RestartSec=5
TimeoutStartSec=3
TimeoutStopSec=3

NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

<!DOCTYPE html>
<html lang="en">
<p>404 error</p>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
import http.server
import socketserver
import threading
import signal
import sys
import os
import json
from datetime import datetime

import mimetypes
# Ensure mimetypes are set for common file types
mimetypes.add_type('application/javascript', '.js')


class PVSystemViewerHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
script_dir = os.path.dirname(os.path.abspath(__file__))
www_dir = os.path.join(script_dir, 'www') # Path to the www folder

if self.path == '/':
file_path = os.path.join(www_dir, 'systemViewer.html')
else:
# Serve the requested file
file_path = os.path.join(www_dir, self.path.lstrip('/'))

# Check if the file exists and is within the www directory
if os.path.commonpath([www_dir, os.path.abspath(file_path)]) != www_dir or not os.path.isfile(file_path):
self.send_response(404)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(b'<!DOCTYPE html><html lang="en"><p>404 Not Found</p></body></html>')
return

# Determine the MIME type of the file
mime_type, _ = mimetypes.guess_type(file_path)
print(mime_type)
self.send_response(200)
self.send_header('Content-type', mime_type or 'application/octet-stream')
self.end_headers()

# Serve the file content
with open(file_path, 'rb') as f:
self.wfile.write(f.read())

def log_message(self, format, *args):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"[{timestamp}] {format % args}")

class PVSystemViewerServer:
def __init__(self, port=5804):
self.port = port
self.httpd = None
self.server_thread = None
self.shutdown_event = threading.Event()

def start(self):
try:
self.httpd = socketserver.TCPServer(("", self.port), PVSystemViewerHandler)
# Allow socket reuse to prevent "Address already in use" errors
self.httpd.allow_reuse_address = True

self.server_thread = threading.Thread(target=self.httpd.serve_forever)
self.server_thread.daemon = True
self.server_thread.start()
print(f"PhotonVision System Viewer server started on port {self.port}")
return True
except Exception as e:
print(f"Failed to start server: {e}")
return False

def stop(self):
print("Stopping PhotonVision System Viewer server...")
self.shutdown_event.set()

if self.httpd:
self.httpd.shutdown()
self.httpd.server_close()

if self.server_thread and self.server_thread.is_alive():
self.server_thread.join(timeout=2)

print("PhotonVision System Viewer server stopped")

# Global server instance for signal handler
server_instance = None

def signal_handler(signum, frame):
print(f"\nReceived signal {signum}, stopping server...")
if server_instance:
server_instance.stop()
sys.exit(0)

def main():
global server_instance

# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

server_instance = PVSystemViewerServer(5804)

if server_instance.start():
print("PhotonVision System Viewer service is running. Managed by systemd.")
try:
server_instance.shutdown_event.wait()
except KeyboardInterrupt:
signal_handler(signal.SIGINT, None)
else:
print("Failed to start PhotonVision System Viewer service")
sys.exit(1)

if __name__ == "__main__":
main()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading