Web Server

Discussions on extending SharpCap using the built in Python scripting functionality
Xio1996
Posts: 42
Joined: Fri Feb 21, 2020 10:15 am
Location: Isle of Wight, UK

Re: Web Server

#11

Post by Xio1996 »

Hi,

I have started using MQTT to control SharpCap. I use a two PC system. A laptop connected to the scope and a desktop in my office. The laptop has a Mosquitto Broker installed. I was using Mosquitto on a Raspberry PI, that I use for IOT projects, but decided to have one specifically for SharpCap control.

There is great MQTT python library (pure python) paho.mqtt (https://pypi.org/project/paho-mqtt/) that is very straightforward to integrate and use in SharpCap. I ripped some of the code out of my full script and copied it below as an example of how easy is it to integrate.

I could never get the web server code to work reliably and it always ended up locking and crashing SharpCap when I tried to close it down. If anybody has found a web server solution, I would love to see the code :D

Have fun

Pete

Code: Select all

import sys
import random
import time

# The location of the paho-mqtt python package (install where you like and tell SharpCap where other python packages live)
sys.path.append(r"C:\Users\xxxxx\AppData\Local\MyPythonPackages")

from paho.mqtt import client as mqtt_client

# Mosquitto MQTT Broker
broker = '192.168.0.xxx'
port = 1883

# Script subscribes to the command topic
CommandTopic = "SharpCap/command"

# Script published to the out topic
ResultTopic = "SharpCap/out"

# Generate a Client ID with the subscribe prefix.
client_id = f'subscribe-{random.randint(0, 100)}'

def subscribe(client: mqtt_client):
    def on_message(client, userdata, msg):
        print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")

        SCMsg = msg.payload.decode()
        if len(SCMsg) == 0:
            # Nothing in the message!
            return
        
        # Commands and parameters are passed in a '|' separated string e.g. Capture|captureprofile1|M42 Orion Nebula
        params = SCMsg.split("|")
        print(params)
        
        if len(params) == 0:
            # No parameters! Need at least one command parameter.
            return
        
        # First parameter is the command.
        Cmd = params[0]
		
        if Cmd == "Exit":
            client.disconnect()
        elif Cmd == "Find":
            selectFindMode()
        elif Cmd == "Capture":
            # Check we have enough params - Command, Profile Name, Object ID 
            if len(params) == 3:
                selectCaptureMode(params[1],params[2])
        elif Cmd == "Log":
            ImageDetails = selectSaveAsSeen()
            
            if ImageDetails == "":
                ImageDetails = "FAILED|Logging"
                
            ret = client.publish(ResultTopic, ImageDetails)
        
    client.subscribe(CommandTopic)
    client.on_message = on_message
    print("SharpCap subscribed to topic - " + CommandTopic)

def run():
    client = connect_mqtt()
    subscribe(client)
    client.loop_forever()


if __name__ == '__main__':
    run()
User avatar
admin
Site Admin
Posts: 13350
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Web Server

#12

Post by admin »

Since everyone in this thread is pushing the boundaries with SharpCap scripting, I thought it would be worth asking...

What is the minimum web server built into SharpCap that would be useful?

You could imagine all sorts of possibilities from the very simple (POST a message containing python code and it runs and sends you the output) through to some sort of restful API through to a full web UI. As it moves towards the more complex end, obviously a lot more work, so less likely to happen in the short to medium term. It would be interesting though to work out if there is a simple starting point that might be useful.

cheers,

Robin
metastable
Posts: 45
Joined: Thu Oct 26, 2023 1:24 am

Re: Web Server

#13

Post by metastable »

Here's what I've come up with so far,

Code: Select all

from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import sys
from urllib.parse import urlparse, parse_qs
import os
from pathlib import Path

# from sharpcap import SharpCap
# SharpCap.createMocks()

HOST = "localhost"
PORT = 4001

class RequestHandler(BaseHTTPRequestHandler):
	def do_GET(self):
		
		print('Received request ' + self.path)

		parse_result = urlparse(self.path)
		url_path = parse_result.path
		self.query_params = parse_qs(parse_result.query)
		if url_path == "/cameras":
			cameras = []
			index = 0
			for camera in SharpCap.Cameras:
				cameras.append({ "name": camera.DeviceName, "index": index, "provider": camera.Provider, "IsDirectDriver": camera.IsDirectDriver})
				index += 1
			response = json.dumps(cameras)
			
			self.respond(200, "application/json", response.encode())
		elif url_path == "/shutdown":
			self.respond(200, "text/html", "Success".encode())
			httpd.server_close()
		elif url_path == "/select_camera" and self.has_param('index'):
			index = int(self.get_param('index'))
			try:
				SharpCap.SelectedCamera = SharpCap.Cameras[index]
			except:
				e = sys.exc_info()[0]
				print(e)
				
			self.respond(200, "text/html", "Success".encode())
		elif url_path == "/close_camera":
			SharpCap.SelectedCamera = None
			
			self.respond(200, "text/html", "Success".encode())
		elif url_path == "/capture_frame" and self.has_param('exp_len'):
			path = Path(os.environ['USERPROFILE'], 'AppData\Local\Temp\SharpCap\capture.png')
			os.makedirs(str(path.parent), exist_ok=True)
			
			SharpCap.SelectedCamera.Controls.Exposure.ExposureMs = int(self.get_param('exp_len'))
			if SharpCap.SelectedCamera.CaptureSingleFrameTo(str(path)):
				f = open(path)
				self.respond(200, "image/png", f.read())
				f.close()
			else:
				self.respond(422, "text/html", "Failed to capture".encode())
		else:
			response = "Unknown request "+ self.path
			self.respond(422, "text/html", response.encode())
		
	def has_param(self, expected):
		return expected in self.query_params and len(self.query_params[expected]) == 1
	
	def get_param(self, key):
		return self.query_params[key][0]
		
	def respond(self, code, content_type, data):
		self.send_response(code)
		self.send_header("Content-Type", content_type)
		self.end_headers()
		self.wfile.write(data)
        
httpd = HTTPServer((HOST, PORT), RequestHandler)
httpd.serve_forever()
I'm mostly a C# developer so my python is likely not any good :lol:. This works just fine. You might notice the /shutdown endpoint, I noticed that simply stopping the script would leave the socket open, so I need to explicitly close the server to prevent that. One thing I've been trying to figure out is whether or not I can grab the currently displayed image, that's likely going to be the crutch of my workflow.

As for what the minimum web server, I would think something similar to what I have above, where we can simply call into the SharpCap functions and get the output back.
Xio1996
Posts: 42
Joined: Fri Feb 21, 2020 10:15 am
Location: Isle of Wight, UK

Re: Web Server

#14

Post by Xio1996 »

Hi Robin,

In the first instance, being able to run a python script via a POST or GET would be very useful. With TheSky Professional you pass the script via TCP and receive the output. With AstroPlanner you tell it which script to run (or pass in a script) along with optional parameters (JSON) and get the response and error/status information via a returned JSON document.

The ability to do both would be great. Pass a script or specify a path to a script with a method of passing parameters and returning output to the caller.

This sounds like an exciting project for Monday :D

Have fun.

Pete
metastable
Posts: 45
Joined: Thu Oct 26, 2023 1:24 am

Re: Web Server

#15

Post by metastable »

I wasn't able to see a way to get the in-memory live stack being displayed directly but simply ended up just calling `SharpCap.LiveStacking.SaveFrameAsSeen` and sending that image in the response. At this point I'm working on a front-end client. Perhaps in a couple weeks I'll have enough to be able to test out the full flow one of these nights.
Xio1996
Posts: 42
Joined: Fri Feb 21, 2020 10:15 am
Location: Isle of Wight, UK

Re: Web Server

#16

Post by Xio1996 »

metastable wrote: Mon Oct 30, 2023 2:39 am I wasn't able to see a way to get the in-memory live stack being displayed directly but simply ended up just calling `SharpCap.LiveStacking.SaveFrameAsSeen` and sending that image in the response. At this point I'm working on a front-end client. Perhaps in a couple weeks I'll have enough to be able to test out the full flow one of these nights.
Hi,
I found this in the scripting forum. Hopefully, it might help.

Custom frame transform?
viewtopic.php?p=5672#p5672

Have fun

Pete
User avatar
admin
Site Admin
Posts: 13350
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Web Server

#17

Post by admin »

Hi,

thanks for the feedback - I will think about this a bit more :) Nothing changing for today's release as I already have an exciting new feature ready to go...

For the access to frames, you can access the BeforeFrameDisplay event on the camera, which should give you access to the frame object just before it is sent to the display - this will include live stack calculations, adjustments, etc (the FrameCaptured event gives you the frame straight off the camera, so is before any processing/stacking).

Various ways to get at the data

* you can call 'SaveTo' to save to a file, but you need to also specify an AllowOverwriteDisposition from SharpCap.Base.FileWriters to say if you are happy for it to overwrite an existing file
* You can call ToFloats()/ToBytes()/ToUShorts() to get the data as a .NET array - I think floats should work for all bit depths, other wise bytes for 8 bit, ushorts for 16
* You can ask SharpCap to map the frame data into memory using GetBufferLease() - from the lease you can get the address and length or access the data as a Span<T>. You must call .Dispose() on the lease or things will rapidly grind to a halt

If you are more familiar with C#, you can write a C# DLL with some of the web server stuff as helper functions and then load/call that from Python.

cheers,

Robin
metastable
Posts: 45
Joined: Thu Oct 26, 2023 1:24 am

Re: Web Server

#18

Post by metastable »

Wow! Lots of good information in those last two post! haha. What I have for now is good enough to give my app something to work off of, but I'll definitely be going back and see what's the best way to improve on things. Now that I know there's an event I can hook into I might want to investigate a longer-lived connection so that the web server can send frames to the client as they come in.
metastable
Posts: 45
Joined: Thu Oct 26, 2023 1:24 am

Re: Web Server

#19

Post by metastable »

Another question. Is it possible to control the Polar Align process through the API? For my workflow I would like to be able to control that process through the app I'm building as a way to avoid needing to remote into the PC itself. The only thing I've been able to find related to polar alignment is the `PolarAlignOffset`.
User avatar
admin
Site Admin
Posts: 13350
Joined: Sat Feb 11, 2017 3:52 pm
Location: Vale of the White Horse, UK
Contact:

Re: Web Server

#20

Post by admin »

Hi,

you have some limited control here.

You can activate it by

Code: Select all

SharpCap.Transforms.SelectTransform("Polar Align")
You can't then control it, but if you have previously ticked the 'Skip Introduction' and 'Auto Advance' option, it will automatically move through the stages as the image is rotated, arriving at the adjustment stage once the rotation is far enough.

Once on the adjustment stage, you can read the PolarAlignOffset field, which tells you how far to move the mount to get alignment in Alt/Az co-ordinates (up/down/left/right).

cheers,

Robin
Post Reply