Skip to content

Instantly share code, notes, and snippets.

@JackDesBwa
Last active December 8, 2022 02:13
Show Gist options
  • Save JackDesBwa/f6084d71bcf58633842f6fdaec8b6b32 to your computer and use it in GitHub Desktop.
Save JackDesBwa/f6084d71bcf58633842f6fdaec8b6b32 to your computer and use it in GitHub Desktop.
Scripts for remote access to the QooCam EGO

QooCam EGO remote scripts

This is a collection of independant scripts to show how to communicate with the QooCam EGO remotely.

They were tested with firmware 2.1.29 in the camera.

They are quite minimal so that you can inpire yourself to create your own awesome utility.

You can also use them directly as is.

#!/usr/bin/env python3
# Script to capture a video with remote interface of the QooCam EGO
import urllib.request
import json
import sys
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} <IP>")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=b'{"name":"camera.startCapture"}') as f:
jresponse = json.loads(f.read().decode())
print(jresponse)
input('Type enter to stop')
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=b'{"name":"camera.stopCapture"}') as f:
jresponse = json.loads(f.read().decode())
print(jresponse)
#!/usr/bin/env python3
# Script to download a thumbnail with remote interface of the QooCam EGO
import urllib.request
import json
import sys
if len(sys.argv) < 3:
print(f"usage: {sys.argv[0]} <IP> <path> [other paths]")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
cmd = {
"name": "camera.delete",
"parameters": {
"fileUrls": sys.argv[2:]
}
}
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
jresponse = json.loads(f.read().decode())
print(jresponse)
#!/usr/bin/env python3
# Script to download a file with remote interface of the QooCam EGO
import urllib.request
import json
import sys
if len(sys.argv) != 4:
print(f"usage: {sys.argv[0]} <IP> <path> <out_file>")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
cmd = {
"name": "camera._downloadFile",
"parameters": {
"type": "icon",
"fileName": sys.argv[2]
}
}
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
response = f.read()
if response[0] == '{':
print('Not found')
else:
with open(sys.argv[3], 'wb') as f:
f.write(response)
#!/usr/bin/env python3
# Script to download a thumbnail with remote interface of the QooCam EGO
import urllib.request
import json
import sys
if len(sys.argv) != 4:
print(f"usage: {sys.argv[0]} <IP> <path> <out_file.jpg>")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
cmd = {
"name": "camera._getThumbnail",
"parameters": {
"type": "icon",
"fileName": sys.argv[2]
}
}
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
response = f.read()
if response[0] == '{':
print('Not found')
else:
with open(sys.argv[3], 'wb') as f:
f.write(response)
#!/usr/bin/env python3
# Script to get option/config with remote interface of the QooCam EGO
import urllib.request
import argparse
import json
import sys
options = [
"captureMode",
"captureModeSupport",
"captureStatus",
"captureStatusSupport",
"dateTimeZone",
"exposureCompensation",
"exposureCompensationSupport",
"exposureDelay",
"exposureDelaySupport",
"exposureProgram",
"exposureProgramSupport",
"fileFormat",
"fileFormatSupport",
"imageStabilization",
"imageStabilizationSupport",
"remainingSpace",
"totalSpace",
"whiteBalance",
"whiteBalanceSupport",
"_focusType",
"_focusTypeSupport",
"_lightFrequency",
"_lightFrequencySupport",
"_shutterSpeedMaxLimit",
"_shutterSpeedMaxLimitSupport",
"_temperature",
"_temperatureSupport",
]
parser = argparse.ArgumentParser(description='QooCam EGO get options')
parser.add_argument('ip', nargs=1, help='IP of the device')
for o in options:
parser.add_argument('--'+o, dest=o, action='store_const', const=True)
args = parser.parse_args()
options_to_get = [k for k, v in vars(args).items() if v is not None and k != 'ip']
urllib.request.urlopen(f'http://{args.ip[0]}/osc/info')
cmd = {
"name": "camera.getOptions",
"parameters": {
"optionNames": options_to_get
}
}
with urllib.request.urlopen(f'http://{args.ip[0]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
jresponse = json.loads(f.read().decode())
if 'error' in jresponse:
print('Error!', jresponse['error']['message'])
else:
for k, v in jresponse['results'].items():
print(k+':', v)
#!/usr/bin/env python3
# Script to list files with remote interface of the QooCam EGO
# Equivalent to (+formatting)
# curl -i 'http://${CAMIP}/osc/commands/execute' --data '{"name":"camera.listFiles"}'
import urllib.request
import json
import sys
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} <IP>")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=b'{"name":"camera.listFiles"}') as f:
jresponse = json.loads(f.read().decode())
medias = jresponse['results']['medias']
cmd = {
"name": "camera._getMediasSize",
"parameters": {
"medias": medias
}
}
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
jresponse = json.loads(f.read().decode())
sizes = jresponse['results']['size']
def human_readable(size):
for x in ['B', 'KiB', 'MiB', 'GiB']:
if size < 1024: return f'{size:3.1f} {x}'
size /= 1024
return f'{size:3.1f} GiB'
print('\n'.join((' '*10+human_readable(s))[-10:]+'\t'+p for s,p in zip(sizes, medias)))
#!/usr/bin/env python3
# Script to display live preview of the QooCam EGO in anaglyph
import time
import sys
import cv2
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} <IP>")
exit()
try:
cap = cv2.VideoCapture(int(sys.argv[1]))
except:
cap = cv2.VideoCapture(f"rtsp://{sys.argv[1]}/liveRTSP/av0")
ret, frame = cap.read()
if not ret: exit()
h,w,_ = frame.shape
w2 = w//2
print("""Help
====
q: quit
f: print FPS
g: print dp/dv
a: stereo anaglyph
m: monoscopic (left)
s: switch mono/anaglyph
4/6: change window position
2/8: change vertical error
5: reset dp/dv
""")
show_fps = False
stereo = True
dp = 0
dv = 0
while True:
start = time.time()
ret, frame = cap.read()
if not ret: break
img = frame[-dv if dv < 0 else 0 : -dv if dv > 0 else None, w2 - dp if dp < 0 else w2 : -dp if dp > 0 else None]
if stereo:
b,g,r = cv2.split(frame[dv if dv > 0 else 0 : dv if dv < 0 else None, dp if dp > 0 else 0 : w2 + dp if dp < 0 else w2])
img[:,:,2] = 0.114*b+0.587*g+0.299*r
cv2.imshow('QooCam EGO live anaglyph', img)
key = cv2.waitKey(1)
if key == ord('f'): show_fps = not show_fps
elif key == ord('g'): print(f"dp={dp}, dv={dv}")
elif key == ord('a'): stereo = True
elif key == ord('m'): stereo = False
elif key == ord('s'): stereo = not stereo
elif key == ord('q'): break
elif key == ord('4'): dp = max(1 - w2, dp - 1)
elif key == ord('6'): dp = min(dp + 1, w2 - 1)
elif key == ord('8'): dv = max(1 - h//2, dv - 1)
elif key == ord('2'): dv = min(dv + 1, h//2 - 1)
elif key == ord('5'): dv = dp = 0
end = time.time()
if show_fps:
seconds = end - start
print (f"Time taken : {1/seconds} fps ; {seconds} seconds")
#!/usr/bin/env python3
# Script to get option/config with remote interface of the QooCam EGO
import urllib.request
import argparse
import json
import sys
options = [
("captureMode", str),
("dateTimeZone", str),
("exposureCompensation", float),
("exposureDelay", int),
("exposureProgram", int),
("imageStabilization", str), # Accepted but not used (?)
("whiteBalance", str), # Accepted but not used (?)
("_focusType", str),
("_lightFrequency", str),
("_shutterSpeedMaxLimit", str),
("_temperature", str),
]
parser = argparse.ArgumentParser(description='QooCam EGO set options')
parser.add_argument('ip', nargs=1, help='IP of the device')
for o in options:
parser.add_argument('--'+o[0], dest=o[0], type=o[1], nargs=1)
args = parser.parse_args()
options_to_set = {}
for k, v in vars(args).items():
if v is not None and k != 'ip':
options_to_set[k] = v[0]
urllib.request.urlopen(f'http://{args.ip[0]}/osc/info')
cmd = {
"name": "camera.setOptions",
"parameters": {
"options": options_to_set
}
}
with urllib.request.urlopen(f'http://{args.ip[0]}/osc/commands/execute', data=json.dumps(cmd).encode()) as f:
jresponse = json.loads(f.read().decode())
if 'error' in jresponse:
print('Error!', jresponse['error']['message'])
else:
for k, v in jresponse['results'].items():
print(k+':', v)
#!/usr/bin/env python3
# Script to take a picture with remote interface of the QooCam EGO
# Equivalent to
# curl -i 'http://${CAMIP}/osc/commands/execute' --data '{"name":"camera.takePicture"}'
import urllib.request
import json
import sys
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} <IP>")
exit()
urllib.request.urlopen(f'http://{sys.argv[1]}/osc/info')
with urllib.request.urlopen(f'http://{sys.argv[1]}/osc/commands/execute', data=b'{"name":"camera.takePicture"}') as f:
jresponse = json.loads(f.read().decode())
print(jresponse)
@DeanZwikel
Copy link

Maybe there is a parameter to cut in chunks, or something similar that I was not able to find.

I tried using requests with chunks and it still fails this same way.

@DeanZwikel
Copy link

I did some investigation with a network sniffer. I watched the QooCam Android app communicate with the EGO. It used port 5555 instead of port 80 that I had been using. I tried using 5555 but was unable to get a response from the EGO. I suspect the QooCam app and EGO may be using some type of secure connection. That may be the reason for the difference in behavior. That said its hard to understand how port 80 seems to work for shorter length messages and media files.

@JackDesBwa
Copy link
Author

I updated the firmware to version 2.1.29 and this HTTP interface seems to work differently. 😕

It still says that it is "Kandao Osc Server" in the response's headers, which previously directed me to Google OSC API and despite the differences this helped me to write those scripts, but it seems that the scripts no longer work [not tested every single one ; live preview is independent of this API and still works]. There is a "commMode type mismatch" message on those I tested.

Concerning download of big files, this version has a USB transfer feature, with a transfer rate of about 22MB/s observed.

As for the 5555 port, understanding what is going on this interface might be a significant job.

@DeanZwikel
Copy link

DeanZwikel commented Jul 3, 2022

I had also updated the firmware to version 2.1.29 and can confirm that the HTTP interface works differently.

Through more testing with a sniffer I can confirm that the QooCam Android and iOS Apps communicate with the EGO via port 5555. This is a secure connection. Before communicating with the EGO, the Apps do a TLS handshake with a server in China to obtain credentials. The Apps then uses those credentials to communicate OSC commands to and download files from the EGO over a secure connection.

Some of the OSC commands still work using port 80. So far, I have confirmed that the /osc/info and the /osc/commands/execute camera.listFiles and camera._getMediasSize commands work. Sometimes the /osc/state works. Sometimes it returns an error. The /osc/commands/execute camera._download_File works with Photo files and with smaller Video files. For larger files, the download aborts before completing. Using the sniffer, it appears that the EGO sends a message indicating the file transmission is complete even though there is more to send. Not sure what the upper limit is on Video file size that works. So far I have observed download speed via WiFi of ~ 10 MB/sec. This is a very convenient way to download Photos and smaller Video files. For larger files, the USB interface is probably preferred.

@DeanZwikel
Copy link

DeanZwikel commented Jul 3, 2022

I found that "/osc/commands/execute" request will work for some commands (for example "camera.takePicture") if it is preceeded by an "/osc/info" request. This is also true for an "/osc/state" request. "/osc/info" seems to put the EGO in the correct "commMode".

@DeanZwikel
Copy link

DeanZwikel commented Jul 3, 2022

I think if you add an "/osc/info" request to the beginning of each of your scripts they may work.

@JackDesBwa
Copy link
Author

Good catch.
Updated.

@DeanZwikel
Copy link

DeanZwikel commented Jul 3, 2022

The CURL "--next" option can be used to send the /osc/info and /osc/commands/execute in one command. For example:

curl http://192.168.1.106/osc/info --next http://192.168.1.106/osc/commands/execute -d "{\"name\":\"camera.takePicture\"}"

This is for Windows which requires the " to be escaped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment