diff --git a/README.md b/README.md index 34017de5d..f210bf338 100644 --- a/README.md +++ b/README.md @@ -74,26 +74,34 @@ You will probably need some cables and connectors to connect your camera to the --------- -The code was designed to run on a RPi, but it will also run an some Linux distributions. We have tested it on Linux Mint 18 and Ubuntu 16. +The code was designed to run on a RPi, but it will also run an some Linux distributions. We have tested it on Linux Mint 20 and Ubuntu 20 and 22. The recording **will not** run on Windows, but most of other submodules will (astrometric calibration, viewing the data, manual reduction, etc.). The problem under Windows is that for some reason the logging module object cannot be pickled when parallelized by the multiprocessing library. **We weren't able to solve this issue, but we invite people to try to take a stab at it.** -Here we provide installation instructions for the RPi, but the procedure should be the same for any Debian-based Linux distribution: [LINK](https://docs.google.com/document/d/19ImeNqBTD1ml2iisp5y7CjDrRV33wBeF9rtx3mIVjh4/edit) +Here we provide installation instructions for the RPi, but the procedure should be the same for any Debian-based Linux distribution: [LINK](https://docs.google.com/document/d/e/2PACX-1vTh_CtwxKu3_vxB6YpEoctLpsn5-v677qJgWsYi6gEr_QKacrfrfIz4lFM1l-CZO86t1HwFfk3P5Nb6/pub#h.399xr1c3jau2) Alternatively, if you are using Anaconda Python on your Linux PC, you can install all libraries except OpenCV by running: ``` +conda create --name rms python=3.9 +conda activate rms conda install -y numpy scipy gitpython cython matplotlib conda install -y -c conda-forge Pillow pyqtgraph'<=0.12.1' -conda install -y -c anaconda ephem +conda install -y -c conda-forge ephem conda install -y -c conda-forge imageio pandas +conda install -y -c conda-forge pygobject conda install -y -c astropy astropy +conda install -y pyqt pip install rawpy pip install git+https://github.com/matejak/imreg_dft@master#egg=imreg_dft ``` -To install OpenCV, use the ```opencv4_install.sh``` script. This will build OpenCV with gstreamer and ffmpeg support. +If you want to use the machine for capture, you need to install OpenCV using the ```opencv4_install.sh``` script. This will build OpenCV with gstreamer and ffmpeg support. If you are not planning to run the capture but you are planning to use other RMS tool, you can install opencv using conda: + +``` +conda install -c conda-forge opencv +``` ## Setting up diff --git a/RMS/Formats/FrameInterface.py b/RMS/Formats/FrameInterface.py index db246b785..4e2cc36e8 100644 --- a/RMS/Formats/FrameInterface.py +++ b/RMS/Formats/FrameInterface.py @@ -502,7 +502,16 @@ def loadChunk(self, first_frame=None, read_nframes=None): self.cache[file_name] = self.ff return ff + + + def setCurrentFF(self, ff_name): + """ Set the current FF file. """ + if ff_name in self.ff_list: + self.current_ff_index = self.ff_list.index(ff_name) + + # Load the chunk + self.loadChunk() @property def current_ff_file(self): @@ -709,15 +718,20 @@ def __init__(self, dir_path, config, beginning_time=None, detection=False): if beginning_time is None: + # Try reading the beginning time of the video from the name if time is not given try: - # Try reading the beginning time of the video from the name if time is not given self.beginning_datetime = datetime.datetime.strptime(file_name_noext, "%Y%m%d_%H%M%S.%f") + + except ValueError: - except: - messagebox(title="Input error", \ - message="The time of the beginning cannot be read from the file name! Either change the name of the file to be in the YYYYMMDD_hhmmss format, or specify the beginning time using command line options.") + try: + self.beginning_datetime = datetime.datetime.strptime(file_name_noext, "%Y%m%d_%H%M%S") - sys.exit() + except: + messagebox(title="Input error", \ + message="The time of the beginning cannot be read from the file name! Either change the name of the file to be in the YYYYMMDD_hhmmss format, or specify the beginning time using command line options.") + + sys.exit() else: self.beginning_datetime = beginning_time diff --git a/Scripts/MultiCamLinux/RMS-screen.sh b/Scripts/MultiCamLinux/RMS-screen.sh new file mode 100644 index 000000000..06da2ebca --- /dev/null +++ b/Scripts/MultiCamLinux/RMS-screen.sh @@ -0,0 +1,8 @@ +#!/bin/bash +if [[ -d /home/${USER}/source/Stations ]] +then +for Dir in /home/${USER}/source/Stations/* + do screen -dmS $(basename $Dir) /home/${USER}/source/RMS/Scripts/MultiCamLinux/StartCapture.sh $(basename $Dir) + echo "Launched and detached station $(basename $Dir)" + done +fi diff --git a/Scripts/RMS_Update.sh b/Scripts/RMS_Update.sh index b8ce1e988..99faf8fe7 100755 --- a/Scripts/RMS_Update.sh +++ b/Scripts/RMS_Update.sh @@ -63,6 +63,7 @@ source ~/vRMS/bin/activate if sudo -n true 2>/dev/null; then sudo apt-get update sudo apt-get install -y gobject-introspection libgirepository1.0-dev + sudo apt-get install -y gstreamer1.0-libav gstreamer1.0-plugins-bad else echo "sudo requires a password. Please run this script as a user with passwordless sudo access." fi diff --git a/Utils/CamManager.py b/Utils/CamManager.py new file mode 100644 index 000000000..ac7c57ecb --- /dev/null +++ b/Utils/CamManager.py @@ -0,0 +1,804 @@ +""" +MIT License + +Copyright (c) 2017 Eliot Kent Woodrich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +# based on https://github.com/OpenIPC/python-dvr/blob/master/DeviceManager.py +""" + +from __future__ import print_function, unicode_literals, division, absolute_import + +import os, sys, struct, fcntl, json +from locale import getlocale +from subprocess import check_output +from socket import * +import platform +from datetime import * +import hashlib, base64 + +try: + from dvrip import DVRIPCam + +except ImportError: + print("Exiting: dvrip module not found. This script cannot run on Python 2.") + sys.exit(1) + +try: + try: + from tkinter import * + except: + from Tkinter import * + from tkinter.filedialog import asksaveasfilename, askopenfilename + from tkinter.messagebox import showinfo, showerror + from tkinter.ttk import * + + GUI_TK = True +except: + GUI_TK = False + +# list of preffered interfaces - camera is supposed to be connected to a wired interface +intfs = ['eth0', 'eno1'] +devices = {} +log = "search.log" +icon = "R0lGODlhIAAgAPcAAAAAAAkFAgwKBwQBABQNBRAQDQQFERAOFA4QFBcWFSAaFCYgGAoUMhwiMSUlJCsrKyooJy8wLjUxLjkzKTY1Mzw7OzY3OEpFPwsaSRsuTRUsWD4+QCo8XQAOch0nYB05biItaj9ARjdHYiRMfEREQ0hIR0xMTEdKSVNOQ0xQT0NEUVFNUkhRXlVVVFdYWFxdXFtZVV9wXGZjXUtbb19fYFRda19gYFZhbF5wfWRkZGVna2xsa2hmaHFtamV0Ynp2aHNzc3x8fHh3coF9dYJ+eH2Fe3K1YoGBfgIgigwrmypajDtXhw9FpxFFpSdVpzlqvFNzj0FvnV9zkENnpUh8sgdcxh1Q2jt3zThi0SJy0Dl81Rhu/g50/xp9/x90/zB35TJv8DJ+/EZqzj2DvlGDrlqEuHqLpHeQp26SuhqN+yiC6imH/zSM/yqa/zeV/zik/1aIwlmP0mmayWSY122h3VWb6kyL/1yP8UGU/UiW/VWd/miW+Eqp/12k/1Co/1yq/2Gs/2qr/WKh/nGv/3er9mK3/3K0/3e4+4ODg4uLi4mHiY+Qj5WTjo+PkJSUlJycnKGem6ShnY2ZrKOjo6urrKqqpLi0prS0tLu8vMO+tb+/wJrE+bzf/sTExMfIx8zMzMjIxtrWyM/Q0NXU1NfY193d3djY1uDf4Mnj+931/OTk5Ozs7O/v8PLy8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAgACAAAAj+AAEIHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mgx4iVMnTyJInVKlclSpD550nRpUqKGmD59EjWqlMlVOFWdIgWq0iNNoBIhSujokidPn0aNKrmqVStWqjxRumTqyI5KOxI5OpiIkiakNG2yelqK5alKLSAJgbBBB6RIjArmCKLIkV1HjyZNpTTJFKgSQoI4cGBiBxBIR6QM6TGQxooWL3LwMBwkSJEcLUq8YATDAZAdMkKh+GGpAo0cL1wInJuokSNIeqdeCgLBAoVMR2CEMkHDzAcnTCzsCAKERwsXK3wYKYLIdd6pjh4guCGJw5IpT7R8CeNlCwsikx7+JTJ+PAZlRHXxOgqBAQMTLXj0AAKkJw+eJw6CXGqJyAWNyT8QgZ5rsD2igwYEOOEGH38EEoghgcQhQgJAxISJI/8ZNoQUijiX1yM7NIBAFm3wUcghh9yBhQcCFEBDJ6V8MskKhgERxBGMMILXI7AhsoAAGSgRBRlliLHHHlZgMAAJmLByCiUnfGajFEcgotVzjkhggAYjjBHFFISgkoodSDAwAyStqDIJAELs4CYQQxChVSRTQcJCFWmUyAcghmzCCRgdXCEHEU69VJiNdDmnV0s4rNHFGmzgkUcfhgiShAd0nNHDVAc9YIEFFWxAQgkVpKAGF1yw4UYdc6AhhQohJFiwQAIRPQCHFlRAccMJFCRAgAAVJXDBBAsQEEBHDwUEADs=" +help = """ + Usage: %s [-q] [-n] [Command];[Command];... + -q No output + -n No gui + Command Description + + help This help + echo Just echo + log [filename] Set log file + logLevel [0..100] Set log verbosity + search [brand] Searching devices of [brand] or all + table Table of devices + json JSON String of devices + device [MAC] JSON String of [MAC] + config [MAC] [IP] [MASK] [GATE] [Pasword] - Configure searched divice + """ % os.path.basename( + sys.argv[0] +) +lang, charset = getlocale() + +#locale = {'utf-8'} + +#def _(msg): +# if lang in locale.keys(): +# if msg in locale[lang].keys(): +# return locale[lang][msg] +# return msg + +""" +CODES = { + 100: _("Success"), + 101: _("Unknown error"), + 102: _("Version not supported"), + 103: _("Illegal request"), + 104: _("User has already logged in"), + 105: _("User is not logged in"), + 106: _("Username or Password is incorrect"), + 107: _("Insufficient permission"), + 108: _("Timeout"), + 109: _("Find failed, file not found"), + 110: _("Find success, returned all files"), + 111: _("Find success, returned part of files"), + 112: _("User already exists"), + 113: _("User does not exist"), + 114: _("User group already exists"), + 115: _("User group does not exist"), + 116: _("Reserved"), + 117: _("Message is malformed"), + 118: _("No PTZ protocol is set"), + 119: _("No query to file"), + 120: _("Configured to be enabled"), + 121: _("Digital channel is not enabled"), + 150: _("Success, camera restart required"), + 202: _("User is not logged in"), + 203: _("Incorrect password"), + 204: _("User is illegal"), + 205: _("User is locked"), + 206: _("User is in the blacklist"), + 207: _("User already logged in"), + 208: _("Invalid input"), + 209: _("User already exists"), + 210: _("Object not found"), + 211: _("Object does not exist"), + 212: _("Account in use"), + 213: _("Permission table error"), + 214: _("Illegal password"), + 215: _("Password does not match"), + 216: _("Keep account number"), + 502: _("Illegal command"), + 503: _("Talk channel has ben opened"), + 504: _("Talk channel is not open"), + 511: _("Update started"), + 512: _("Update did not start"), + 513: _("Update data error"), + 514: _("Update failed"), + 515: _("Update succeeded"), + 521: _("Failed to restore default config"), + 522: _("Camera restart required"), + 523: _("Default config is illegal"), + 602: _("Application restart required"), + 603: _("System restart required"), + 604: _("Write file error"), + 605: _("Features are not supported"), + 606: _("Verification failed"), + 607: _("Configuration does not exist"), + 608: _("Configuration parsing error"), +} +""" + +def tolog(s): + print(s) + if logLevel >= 20: + logfile = open(log, "wb") + logfile.write(bytes(s, "utf-8")) + logfile.close() + + +def get_nat_ip(): + s = socket(AF_INET, SOCK_DGRAM) + try: + # doesn't even have to be reachable + s.connect(("10.255.255.255", 1)) + IP = s.getsockname()[0] + except Exception: + IP = "127.0.0.1" + finally: + s.close() + return IP + + +def local_ip(): + ip = get_nat_ip() + ipn = struct.unpack(">I", inet_aton(ip)) + return ( + inet_ntoa(struct.pack(">I", ipn[0] + 10)), + "255.255.255.0", + inet_ntoa(struct.pack(">I", (ipn[0] & 0xFFFFFF00) + 1)), + ) + + +def get_ip_address(ifname): + server = socket(AF_INET, SOCK_DGRAM) + return inet_ntoa(fcntl.ioctl( + server.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', bytes(ifname[:15], 'utf-8')) + )[20:24]) + + +def sofia_hash(password): + md5 = hashlib.md5(bytes(password, "utf-8")).digest() + chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + return "".join([chars[sum(x) % 62] for x in zip(md5[::2], md5[1::2])]) + + +def GetIP(s): + return inet_ntoa(struct.pack("I", int(s, 16))) + + +def SetIP(ip): + return "0x%08X" % struct.unpack("I", inet_aton(ip)) + + +def GetAllAddr(): + if os.name == "nt": + return [ + x.split(":")[1].strip() + for x in str(check_output(["ipconfig"]), "866").split("\r\n") + if "IPv4" in x + ] + else: + iptool = ["ip", "address"] + if platform.system() == "Darwin": + iptool = ["ifconfig"] + return [ + x.split("/")[0].strip().split(" ")[1] + for x in str(check_output(iptool), "ascii").split("\n") + if "inet " in x and "127.0." not in x + ] + + +def SearchXM(devices): + # pick the first wired interface + det_intfs = list(zip(*if_nameindex()))[1] + print("detected network interfaces:", det_intfs) + intfsf = list(filter(lambda i: i in intfs, det_intfs)) + #print("prefferd interfaces:", intfsf) + intf = intfs[0] + server = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) + + # hack to use eth0 interface instantly + print("Interface:", intf) + print("IP:", get_ip_address(intf)) + server.bind(('', 34569)) + print("binded") + server.settimeout(3) + server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + server.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + server.setsockopt(SOL_SOCKET, 25, intf.encode('utf-8') + '\0'.encode('utf-8')) + server.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, 1) + server.sendto( + struct.pack("BBHIIHHI", 255, 0, 0, 0, 0, 0, 1530, 0), ("255.255.255.255", 34569) + ) + print("sent") + while True: + data = server.recvfrom(1024) + head, ver, typ, session, packet, info, msg, leng = struct.unpack( + "BBHIIHHI", data[0][:20] + ) + if (msg == 1531) and leng > 0: + answer = json.loads( + data[0][20 : 20 + leng].replace(b"\x00", b"")) + if answer["NetWork.NetCommon"]["MAC"] not in devices.keys(): + devices[answer["NetWork.NetCommon"]["MAC"]] = answer[ + "NetWork.NetCommon" + ] + devices[answer["NetWork.NetCommon"]["MAC"]][u"Brand"] = u"xm" + server.close() + return devices + + +def ConfigXM(data): + config = {} + #TODO: may be just copy whwole devices[data[1]] to config? + for k in [u"HostName",u"HttpPort",u"MAC",u"MaxBps",u"MonMode",u"SSLPort",u"TCPMaxConn",u"TCPPort",u"TransferPlan",u"UDPPort","UseHSDownLoad"]: + if k in devices[data[1]]: + config[k] = devices[data[1]][k] + print(devices[data[1]][u"HostName"]) + config[u"DvrMac"] = devices[data[1]][u"MAC"] + config[u"EncryptType"] = 1 + config[u"GateWay"] = SetIP(data[4]) + config[u"HostIP"] = SetIP(data[2]) + config[u"Submask"] = SetIP(data[3]) + config[u"Username"] = "admin" + config[u"Password"] = sofia_hash(data[5]) + devices[data[1]][u"GateWay"] = config[u"GateWay"] + devices[data[1]][u"HostIP"] = config[u"HostIP"] + devices[data[1]][u"Submask"] = config[u"Submask"] + config = json.dumps( + config, ensure_ascii=False, sort_keys=True, separators=(", ", " : ") + ).encode("utf8") + server = socket(AF_INET, SOCK_DGRAM) + server.bind(("", 34569)) + server.settimeout(1) + server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + server.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + clen = len(config) + print(struct.pack( + "BBHIIHHI%ds2s" % clen, + 255, + 0, + 254, + 0, + 0, + 0, + 1532, + clen + 2, + config, + b"\x0a\x00", + ),) + server.sendto( + struct.pack( + "BBHIIHHI%ds2s" % clen, + 255, + 0, + 254, + 0, + 0, + 0, + 1532, + clen + 2, + config, + b"\x0a\x00", + ), + ("255.255.255.255", 34569), + ) + answer = {"Ret": 203} + e = 0 + while True: + try: + data = server.recvfrom(1024) + head, ver, typ, session, packet, info, msg, leng = struct.unpack( + "BBHIIHHI", data[0][:20] + ) + if (msg == 1533) and leng > 0: + answer = json.loads( + data[0][20 : 20 + leng].replace(b"\x00", b"")) + break + except: + e += 1 + if e > 3: + break + server.close() + return answer + + +def ProcessCMD(cmd): + global log, logLevel, devices, searchers, configure, flashers + if logLevel == 20: + tolog(datetime.now().strftime("[%Y-%m-%d %H:%M:%S] >") + " ".join(cmd)) + if cmd[0].lower() == "q" or cmd[0].lower() == "quit": + sys.exit(1) + if cmd[0].lower() in ["help", "?", "/?", "-h", "--help"]: + return help + if cmd[0].lower() == "search": + tolog("%s" % ("Search")) + if len(cmd) > 1 and cmd[1].lower() in searchers.keys(): + try: + devices = searchers[cmd[1].lower()](devices) + except Exception as error: + print(" ".join([str(x) for x in list(error.args)])) + print("Searching %s, found %d devices" % (cmd[1], len(devices))) + else: + for s in searchers: + tolog("Search" + " %s\r" % s) + try: + devices = searchers[s](devices) + except Exception as error: + print(" ".join([str(x) for x in list(error.args)])) + tolog("Found %d devices" % len(devices)) + if len(devices) > 0: + if logLevel > 0: + cmd[0] = "table" + print("") + if cmd[0].lower() == "table": + logs = ( + "Vendor" + + "\t" + + "MAC Address" + + "\t\t" + + "Name" + + "\t" + + "IP Address" + + "\t" + + "Port" + + "\n" + ) + for dev in devices: + logs += "%s\t%s\t%s\t%s\t%s\n" % ( + devices[dev]["Brand"], + devices[dev]["MAC"], + devices[dev]["HostName"], + GetIP(devices[dev]["HostIP"]), + devices[dev]["TCPPort"], + ) + if logLevel >= 20: + tolog(logs) + if logLevel >= 10: + return logs + if cmd[0].lower() == "csv": + logs = ( + _("Vendor") + + ";" + + _("MAC Address") + + ";" + + _("Name") + + ";" + + _("IP Address") + + ";" + + _("Port") + + ";" + + _("SN") + + "\n" + ) + for dev in devices: + logs += "%s;%s;%s;%s;%s;%s\n" % ( + devices[dev]["Brand"], + devices[dev]["MAC"], + devices[dev]["HostName"], + GetIP(devices[dev]["HostIP"]), + devices[dev]["TCPPort"], + devices[dev]["SN"], + ) + if logLevel >= 20: + tolog(logs) + if logLevel >= 10: + return logs + if cmd[0].lower() == "html": + logs = ( + "\r\n" + ) + for dev in devices: + logs += ( + "\r\n" + % ( + devices[dev]["Brand"], + devices[dev]["MAC"], + devices[dev]["HostName"], + GetIP(devices[dev]["HostIP"]), + devices[dev]["TCPPort"], + devices[dev]["SN"], + ) + ) + logs += "
" + + _("Vendor") + + "" + + _("MAC Address") + + "" + + _("Name") + + "" + + _("IP Address") + + "" + + _("Port") + + "" + + _("SN") + + "
%s%s%s%s%s%s
\r\n" + if logLevel >= 20: + tolog(logs) + if logLevel >= 10: + return logs + if cmd[0].lower() == "json": + logs = json.dumps(devices) + if logLevel >= 20: + tolog(logs) + if logLevel >= 10: + return logs + if cmd[0].lower() == "device": + if len(cmd) > 1 and cmd[1] in devices.keys(): + return json.dumps(devices[cmd[1]]) + else: + return "device [MAC]" + + if cmd[0].lower() == "config": + if ( + len(cmd) > 5 + and cmd[1] in devices.keys() + and devices[cmd[1]]["Brand"] in configure.keys() + ): + return configure[devices[cmd[1]]["Brand"]](cmd) + else: + return "config [MAC] [IP] [MASK] [GATE] [Pasword]" + + if cmd[0].lower() == "flash": + if ( + len(cmd) > 3 + and cmd[1] in devices.key(s) + and devices[cmd[1]]["Brand"] in flashers.keys() + ): + if len(cmd) == 4: + cmd[4] = tolog + return flashers[devices[cmd[1]]["Brand"]](cmd) + else: + return "flash [MAC] [password] [file]" + if cmd[0].lower() == "loglevel": + if len(cmd) > 1: + logLevel = int(cmd[1]) + else: + return "loglevel [int]" + if cmd[0].lower() == "log": + if len(cmd) > 1: + log = " ".join(cmd[1:]) + else: + return "log [filename]" + if cmd[0].lower() == "echo": + if len(cmd) > 1: + return " ".join(cmd[1:]) + return "" + + +class GUITk: + def __init__(self, root): + self.root = root + self.root.wm_title("RMS Camera Hunter") + self.root.tk.call("wm", "iconphoto", root._w, PhotoImage(data=icon)) + self.f = Frame(self.root) + self.f.pack(fill=BOTH, expand=YES) + + self.f.columnconfigure(0, weight=1) + self.f.rowconfigure(0, weight=1) + + self.fr = Frame(self.f) + self.fr.grid(row=0, column=0, columnspan=3, sticky="nsew") + self.fr_tools = Frame(self.f) + self.fr_tools.grid(row=1, column=0, columnspan=6, sticky="ew") + self.fr_config = Frame(self.f) + self.fr_config.grid(row=0, column=5, sticky="nsew") + + self.fr.columnconfigure(0, weight=1) + self.fr.rowconfigure(0, weight=1) + + self.table = Treeview(self.fr, show="headings", selectmode="browse", height=8) + self.table.grid(column=0, row=0, sticky="nsew") + self.table["columns"] = ("ID", "vendor", "addr", "port", "name", "mac", "sn") + self.table["displaycolumns"] = ("addr", "name", "mac", "sn") + + self.table.heading("vendor", text="Vendor", anchor="w") + self.table.heading("addr", text="IP Address", anchor="w") + self.table.heading("port", text="Port", anchor="w") + self.table.heading("name", text="Name", anchor="w") + self.table.heading("mac", text="MAC Address", anchor="w") + self.table.heading("sn", text="SN", anchor="w") + + self.table.column("vendor", stretch=0, width=50) + self.table.column("addr", stretch=0, width=110) + self.table.column("port", stretch=0, width=50) + self.table.column("name", stretch=0, width=80) + self.table.column("mac", stretch=0, width=130) + self.table.column("sn", stretch=0, width=120) + + self.scrollY = Scrollbar(self.fr, orient=VERTICAL) + self.scrollY.config(command=self.table.yview) + self.scrollY.grid(row=0, column=1, sticky="ns") + self.scrollX = Scrollbar(self.fr, orient=HORIZONTAL) + self.scrollX.config(command=self.table.xview) + self.scrollX.grid(row=1, column=0, sticky="ew") + self.table.config( + yscrollcommand=self.scrollY.set, xscrollcommand=self.scrollX.set + ) + + self.table.bind("", self.select) + self.popup_menu = Menu(self.table, tearoff=0) + self.popup_menu.add_command( + label="Copy SN", + command=lambda: ( + self.root.clipboard_clear() + or self.root.clipboard_append( + self.table.item(self.table.selection()[0], option="values")[6] + ) + ) + if len(self.table.selection()) > 0 + else None, + ) + self.popup_menu.add_command( + label="Copy line", + command=lambda: ( + self.root.clipboard_clear() + or self.root.clipboard_append( + "\t".join( + self.table.item(self.table.selection()[0], option="values")[1:] + ) + ) + ) + if len(self.table.selection()) > 0 + else None, + ) + self.table.bind("", self.popup) + + self.l0 = Label(self.fr_config, text="Name") + self.l0.grid(row=0, column=0, pady=3, padx=5, sticky=W + N) + self.name = Entry(self.fr_config, width=15, font="6") + self.name.grid(row=0, column=1, pady=3, padx=5, sticky=W + N) + self.l1 = Label(self.fr_config, text="IP Address") + self.l1.grid(row=1, column=0, pady=3, padx=5, sticky=W + N) + self.addr = Entry(self.fr_config, width=15, font="6") + self.addr.grid(row=1, column=1, pady=3, padx=5, sticky=W + N) + self.l2 = Label(self.fr_config, text="Mask") + self.l2.grid(row=2, column=0, pady=3, padx=5, sticky=W + N) + self.mask = Entry(self.fr_config, width=15, font="6") + self.mask.grid(row=2, column=1, pady=3, padx=5, sticky=W + N) + self.l3 = Label(self.fr_config, text="Gateway") + self.l3.grid(row=3, column=0, pady=3, padx=5, sticky=W + N) + self.gate = Entry(self.fr_config, width=15, font="6") + self.gate.grid(row=3, column=1, pady=3, padx=5, sticky=W + N) + self.aspc = Button(self.fr_config, text="As on PC", command=self.addr_pc) + #self.aspc.grid(row=4, column=1, pady=3, padx=5, sticky="ew") + self.l4 = Label(self.fr_config, text="HTTP Port") + #self.l4.grid(row=5, column=0, pady=3, padx=5, sticky=W + N) + self.http = Entry(self.fr_config, width=5, font="6") + #self.http.grid(row=5, column=1, pady=3, padx=5, sticky=W + N) + self.l5 = Label(self.fr_config, text="TCP Port") + #self.l5.grid(row=6, column=0, pady=3, padx=5, sticky=W + N) + self.tcp = Entry(self.fr_config, width=5, font="6") + #self.tcp.grid(row=6, column=1, pady=3, padx=5, sticky=W + N) + self.l6 = Label(self.fr_config, text="Password") + self.l6.grid(row=7, column=0, pady=3, padx=5, sticky=W + N) + self.passw = Entry(self.fr_config, width=15, font="6") + self.passw.grid(row=7, column=1, pady=3, padx=5, sticky=W + N) + self.aply = Button(self.fr_config, text="Apply", command=self.setconfig) + self.aply.grid(row=8, column=1, pady=3, padx=5, sticky="ew") + + #self.l7 = Label(self.fr_tools, text=_("Vendor")) + #self.l7.grid(row=0, column=0, pady=3, padx=5, sticky="wns") + self.ven = Combobox(self.fr_tools, width=10) + #self.ven.grid(row=0, column=1, padx=5, sticky="w") + self.ven["values"] = ["XM",] + self.ven.current(0) + self.search = Button(self.fr_tools, text="Search", command=self.search) + self.search.grid(row=0, column=2, pady=5, padx=5, sticky=W + N) + self.reset = Button(self.fr_tools, text="Reset", command=self.clear) + self.reset.grid(row=0, column=3, pady=5, padx=5, sticky=W + N) + self.exp = Button(self.fr_tools, text="Export", command=self.export) + self.exp.grid(row=0, column=4, pady=5, padx=5, sticky=W + N) + #self.fl_state = StringVar(value=_("Flash")) + #self.fl = Button(self.fr_tools, textvar=self.fl_state, command=self.flash) + #self.fl.grid(row=0, column=5, pady=5, padx=5, sticky=W + N) + + def popup(self, event): + try: + self.popup_menu.tk_popup(event.x_root, event.y_root, 0) + finally: + self.popup_menu.grab_release() + + def addr_pc(self): + _addr, _mask, _gate = local_ip() + self.addr.delete(0, END) + self.addr.insert(END, _addr) + self.mask.delete(0, END) + self.mask.insert(END, _mask) + self.gate.delete(0, END) + self.gate.insert(END, _gate) + + def search(self): + self.clear() + #if self.ven["values"].index(self.ven.get()) == 0: + ProcessCMD(["search"]) + #else: + # ProcessCMD(["search", self.ven.get()]) + self.pop() + + def pop(self): + for dev in devices: + self.table.insert( + "", + "end", + values=( + dev, + devices[dev]["Brand"], + GetIP(devices[dev]["HostIP"]), + devices[dev]["TCPPort"], + devices[dev]["HostName"], + devices[dev]["MAC"], + devices[dev]["SN"], + ), + ) + + def clear(self): + global devices + for i in self.table.get_children(): + self.table.delete(i) + devices = {} + + def select(self, event): + if len(self.table.selection()) == 0: + return + dev = self.table.item(self.table.selection()[0], option="values")[0] + if logLevel >= 20: + print(json.dumps(devices[dev], indent=4, sort_keys=True)) + self.name.delete(0, END) + self.name.insert(END, devices[dev]["HostName"]) + self.addr.delete(0, END) + self.addr.insert(END, GetIP(devices[dev]["HostIP"])) + self.mask.delete(0, END) + self.mask.insert(END, GetIP(devices[dev]["Submask"])) + self.gate.delete(0, END) + self.gate.insert(END, GetIP(devices[dev]["GateWay"])) + self.http.delete(0, END) + self.http.insert(END, devices[dev]["HttpPort"]) + self.tcp.delete(0, END) + self.tcp.insert(END, devices[dev]["TCPPort"]) + + def setconfig(self): + dev = self.table.item(self.table.selection()[0], option="values")[0] + devices[dev][u"TCPPort"] = int(self.tcp.get()) + devices[dev][u"HttpPort"] = int(self.http.get()) + devices[dev][u"HostName"] = self.name.get() + result = ProcessCMD( + [ + "config", + dev, + self.addr.get(), + self.mask.get(), + self.gate.get(), + self.passw.get(), + ] + ) + if result["Ret"] == 100: + self.table.item( + self.table.selection()[0], + values=( + dev, + devices[dev]["Brand"], + GetIP(devices[dev]["HostIP"]), + devices[dev]["TCPPort"], + devices[dev]["HostName"], + devices[dev]["MAC"], + devices[dev]["SN"], + ), + ) + else: + showerror("Error"), CODES[result["Ret"]] + + def export(self): + filename = asksaveasfilename( + filetypes=( + ("JSON files"), "*.json", + ("HTML files"), "*.html;*.htm", + ("Text files"), "*.csv;*.txt", + ("All files"), "*.*", + ) + ) + if filename == "": + return + ProcessCMD(["log", filename]) + ProcessCMD(["loglevel", str(100)]) + if ".json" in filename: + ProcessCMD(["json"]) + elif ".csv" in filename: + ProcessCMD(["csv"]) + elif ".htm" in filename: + ProcessCMD(["html"]) + else: + ProcessCMD(["table"]) + ProcessCMD(["loglevel", str(10)]) + + def flash(self): + self.fl_state.set("Processing...") + filename = askopenfilename( + filetypes=("Flash", "*.bin", "All files", "*.*") + ) + if filename == "": + return + if len(self.table.selection()) == 0: + _mac = "all" + else: + _mac = self.table.item(self.table.selection()[0], option="values")[4] + result = ProcessCMD( + ["flash", _mac, self.passw.get(), filename, self.fl_state.set] + ) + if ( + hasattr(result, "keys") + and "Ret" in result.keys() + and result["Ret"] in CODES.keys() + ): + showerror("Error", CODES[result["Ret"]]) + + +searchers = { + #"wans": SearchWans, + "xm": SearchXM, + #"dahua": SearchDahua, + #"fros": SearchFros, + #"beward": SearchBeward, +} +configure = { + #"wans": ConfigWans, + "xm": ConfigXM, + #"fros": ConfigFros, +} # ,"dahua":ConfigDahua +#flashers = {"xm": FlashXM} # ,"dahua":FlashDahua,"fros":FlashFros +logLevel = 30 +if __name__ == "__main__": + if len(sys.argv) > 1: + cmds = " ".join(sys.argv[1:]) + if cmds.find("-q ") != -1: + cmds = cmds.replace("-q ", "").replace("-n ", "").strip() + logLevel = 0 + for cmd in cmds.split(";"): + ProcessCMD(cmd.split(" ")) + if GUI_TK and "-n" not in sys.argv: + root = Tk() + app = GUITk(root) + if ( + "--theme" in sys.argv + ): # ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative') + style = Style() + theme = [sys.argv.index("--theme") + 1] + if theme in style.theme_names(): + style.theme_use(theme) + root.mainloop() + sys.exit(1) + print("Type help or ? to display help(q or quit to exit)") + while True: + data = input("> ").split(";") + for cmd in data: + result = ProcessCMD(cmd.split(" ")) + if hasattr(result, "keys") and "Ret" in result.keys(): + print(CODES[result["Ret"]]) + else: + print(result) + sys.exit(1) + diff --git a/Utils/SkyFit2.py b/Utils/SkyFit2.py index 10ce4369a..7aada9d1a 100644 --- a/Utils/SkyFit2.py +++ b/Utils/SkyFit2.py @@ -15,6 +15,7 @@ import matplotlib.pyplot as plt import pyqtgraph as pg +import RMS from RMS.Astrometry.ApplyAstrometry import xyToRaDecPP, raDecToXYPP, \ rotationWrtHorizon, rotationWrtHorizonToPosAngle, computeFOVSize, photomLine, photometryFit, \ rotationWrtStandard, rotationWrtStandardToPosAngle, correctVignetting, \ @@ -2167,7 +2168,6 @@ def saveState(self): # Currently, any important variables should be initialized in the constructor (and cannot be classes # that inherit). Anything that can be generated for that information should be done in setupUI. - img_name = self.img_handle.name() to_remove = [] dic = copy.copy(self.__dict__) @@ -2183,9 +2183,14 @@ def saveState(self): for remove in to_remove: del dic[remove] - if os.path.isdir(self.input_path): - real_input_path = os.path.join(self.input_path, img_name) - dic['input_path'] = real_input_path + # if os.path.isdir(self.input_path): + # real_input_path = os.path.join(self.input_path, img_name) + # dic['input_path'] = real_input_path + + # Save the FF file name if the input type is FF + if self.img_handle.input_type == 'ff': + dic['ff_file'] = self.img_handle.name() + savePickle(dic, self.dir_path, 'skyFitMR_latest.state') print("Saved state to file") @@ -2270,6 +2275,9 @@ def loadState(self, dir_path, state_name, beginning_time=None): # Set the dir path in case it changed self.dir_path = dir_path + # Update the RMS root directory + self.config.rms_root_dir = os.path.abspath(os.path.join(os.path.dirname(RMS.__file__), os.pardir)) + # Update img_handle parameters if hasattr(self, "img_handle"): @@ -2277,6 +2285,16 @@ def loadState(self, dir_path, state_name, beginning_time=None): # Update the dir path self.img_handle.dir_path = dir_path + # If the input type is FF and the path to the actual FF file got saved, update it + if self.img_handle.input_type == 'ff': + if "ff_file" in variables: + + # If the file is available in the input path, update the FF file path + ff_file = variables["ff_file"] + + if os.path.isfile(os.path.join(dir_path, ff_file)): + self.img_handle.setCurrentFF(ff_file) + # Make sure an option is not missing if self.img_handle.input_type == 'images': if not hasattr(self.img_handle, "fripon_mode"): @@ -3943,6 +3961,11 @@ def loadCatalogStars(self, lim_mag): """ + # If the star catalog path doesn't exist, use the catalog available in the repository + if not os.path.isdir(self.config.star_catalog_path): + self.config.star_catalog_path = os.path.join(self.config.rms_root_dir, 'Catalogs') + print("Updated catalog path to: ", self.config.star_catalog_path) + # Load catalog stars catalog_stars, self.mag_band_string, self.config.star_catalog_band_ratios = StarCatalog.readStarCatalog( self.config.star_catalog_path, self.config.star_catalog_file, lim_mag=lim_mag, diff --git a/requirements.txt b/requirements.txt index 73664e027..cd0c6b1e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,8 @@ gitpython>=2.1.8 paramiko==2.4.0,<=2.8.1; python_version=='2.7' paramiko>=2.4.2,<=2.8.1; python_version>='3.6' numpy>=1.13.3,<1.22.0 ; python_version=='2.7' -numpy>=1.21.0,<1.24.0 ; python_version>='3.6' +numpy>=1.21.0,<1.24.0 ; python_version>='3.6' and python_version<'3.9' +numpy>=1.26.0 ; python_version>='3.9' matplotlib>=2.1.1 pyephem>=3.7.6.0 cython>=0.27.3