diff --git a/HISTORY.rst b/HISTORY.rst index f137e0cc63fde1b73353225cf6deb0dccf382e3a..6c2566e740cc7a53196c97b80c2e485d368dc841 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,88 +2,51 @@ History ======= -2016.288 (2016-10-14) ------------------- -* New. No changes. - -2016.355 (2016-12-20) ------------------- -* Just changed some help descriptions a little. -* Added the -H "more" help. - -2017.006 (2017-01-06) ------------------- -* New -V command for examining offloaded files a bit more. -* Added a bit more to the -H help. - -2017.012 (2017-01-12) ------------------- -* Added a summary to the -V command output. - -2017.020 (2017-01-20) ------------------- -* Added baler time range (all of the files) to the -V summary. -* A couple little corrections to the help. - -2017.024 (2017-01-24) ------------------- -* Added some more -H command help. -* Made some of the status messages a little more wordy. -* Commands like -F "DT0001*" "DT0002*" did not work. Having multiple search - patterns would fail. "DT0002*" by itself, for example, would have worked. - Now it all works. - -2017.026 (2017-01-26) ------------------- -* Rewrote all of the command line handling stuff. There could be bugs - all over the place. -* Added the -m "A message" command. -* Added the -e [<files>] command for excluding files, but then only - offloading low sample rate files (like -o). -E offloads all files - (like -O). - -2017.038 (2017-02-07) ------------------- -* Prints out the current version when using the -U command. -* Put in note about using "bline.py" or "bline" in the helps. - -2017.039 (2017-02-08) ------------------- -* Fixed a bug in the -v command that showed up when files were missing - or not everything had been offloaded. -* Took the note back out about bline/bline.py. It will always be - bline.py. - -2017.055 (2017-02-24) ------------------- -* Changed the way the record size of a file is determined. It should - always be 4096 bytes for baler files, but you never know. -* Fixed it so you have to at least supply the TagID when using the - -m command. - -2017.152 (2017-06-01) ------------------- -* Does a simple check to see if the TagID and IPAddr are reversed. -* Does a simple check when offloading to see if previously offloaded - data files will be overwritten. It may report false alarms depending - on the command. -* Fixed a small bug in the -F command. - -2017.163 (2017-06-12) ------------------- -* Lists the command line being executed so it gets into the log. - -2018.071 (2018-03-12) ------------------- -* Small bug in the 'check for updates' section. - -2018.135 (2018-06-07) ------------------- -* First release on new build system. - 2019.064 (2019-03-05) ------------------- +--------------------- * Converted for Python 2 or 3. * Added -vl command that lists the files that have not been fully offloaded from the baler. -v just shows how many there are. * Does not yet contain BaleAddr replacement code. + +2019.259 (2019-09-16) +--------------------- +* New version (really BLINE2) that assigns the IP address to the baler, + and erases data and Q330 association from the baler, thus removing + the need for BaleAddr. +* The TagID of the baler to communicate with is still required on the + command line, but the IP address of the baler is no longer required for + every command on the command line. +* The Ethernet device name may be required when assigning the IP address + to the baler (-b command) if BLINE says so (generally Python 2). +* These non-regular Python modules may need to be installed to get all + of the new commands (-b, -X, -s) to work: + psutil - all OSs and Python versions + pexpect - Linux + subprocess32 - all OSs when using Python 2 + Python 3 is recommended, since support for 2 is going away soon. + pip/pip3 may be used to install the required modules. + +2019.269 (2019-09-26) +--------------------- +* Caught a different program having trouble checking for updates. Put + in some code as a possible fix. +* Collected the imports for all of the extra required modules into one + place so all of the warnings come out at the same time. Use + bline.py 5555 -b + to get a list of the warnings. 5555 may be any number for this test. +* Fixed up the long version of the Help a bit. + +2019.289 (2019-10-16) +--------------------- +* Added the -A command that will combine all of the offloaded files + into a .ALL file. +* Added the -G command that will combine all of the files for a + channel into one file. The file names are close to the miniseed + file names produced by sdrsplit. +* Changed the command line switch for a couple commands. + +2019.297 (2019-10-24) +--------------------- +* Enhanced the -G command and added the -GD command for concatenating + the offloaded channel files. diff --git a/bline/__init__.py b/bline/__init__.py index 711d13e65134b22cc03940982c6729341d454804..2bc3b91597f9489a758e79f5a86a2a8d9b307af0 100644 --- a/bline/__init__.py +++ b/bline/__init__.py @@ -4,4 +4,4 @@ __author__ = """IRIS PASSCAL""" __email__ = 'software-support@passcal.nmt.edu' -__version__ = '2018.135' +__version__ = '2019.289' diff --git a/bline/bline.py b/bline/bline.py index ec67d11b4c0e245415cb721fd8b59b2e3b9c1c51..0a132e5056844ec7b214e0f14141938e18170691 100755 --- a/bline/bline.py +++ b/bline/bline.py @@ -1,4 +1,5 @@ #! /usr/bin/env python +# -*- coding: latin-1 -*- # BEGIN PROGRAM: BLINE # By: Bob Greschke # Started: 2016.105 @@ -9,12 +10,11 @@ from sys import argv, exit, platform, stdout PROGSystem = platform[:3].lower() PROG_NAME = "BLINE" PROG_NAMELC = "bline" -PROG_VERSION = "2019.064" -PROG_LONGNAME = "Command Line Baler Offload Program" +PROG_VERSION = "2019.297" +PROG_LONGNAME = "Command Line Baler Control Program" PROG_SETUPSVERS = "A" -# These are for 'Check For Updates' stuff that will eventually be replaced -# by just using git. +# These are for the 'Check For Updates' stuff. VERS_VERSURL = "https://www.passcal.nmt.edu/~bob/passoft/" VERS_PARTS = 4 VERS_NAME = 0 @@ -24,7 +24,7 @@ VERS_ZSIZ = 3 ########################## # BEGIN: versionChecksCL() -# LIB:versionChecksCL():2019.023 +# LIB:versionChecksCL():2019.213 # Checks the current version of Python and sets up a couple of things for the # rest of the program to use. # Obviously this is not a real function. It's just a collection of things for @@ -45,12 +45,14 @@ if PROG_PYVERSION.startswith("2"): astring = basestring anint = (int, long) arange = xrange + aninput = raw_input elif PROG_PYVERSION.startswith("3"): PROG_PYVERS = 3 from urllib.request import urlopen, urlretrieve astring = str anint = int arange = range + aninput = input else: stdout.write("Unsupported Python version: %s\nStopping.\n"%PROG_PYVERSION) exit(0) @@ -61,586 +63,898 @@ maxInt = 1E100 maxFloat = 1.0E100 # END: versionChecksCL -from struct import unpack +from copy import deepcopy from fnmatch import fnmatch -from inspect import stack -if PROGSystem == "dar" or PROGSystem == "lin" or PROGSystem == "sun": - from os import getuid from os import makedirs, listdir, sep from os.path import abspath, basename, exists, getsize, isdir -from socket import setdefaulttimeout, gethostbyname, gethostname -#setdefaulttimeout(20) for now. Seems to work better. from time import gmtime, localtime, sleep, strftime, time -# These are for some of the functions that are shared with bgoff.py. They are -# not used in bline.py. -STATE_IDLE = 0 -STATE_CHECK = 1 -STATE_OFFLD = 2 -STATE_STOP = 99 -BState = 0 - -PROGKiosk = False -PROGSmallScreen = False -ArgCheck = False -ArgList = False -ArgVerify = False -ArgVerifyList = False -ArgBadBlocks = False -ArgLSROffload = False -ArgSkipo = False -ArgSkipO = False -ArgSpec = False -ArgSpecFiles = [] -ArgUCheck = False -ArgUDown = False -WriteMsg = False -# These lovely bugs are brought to you by Microsoft. Python 3 was keeping -# ' -#' for arguments, "" as an argument, or not passing command line -# arguments at all. It's related to the registry value -# HKEY_CLASSES_ROOT\Applications\python.exe\shell\open\command -# not having a %* (no quotes around it) at the end of the Data command to -# get Python to pass command line arguments when using 'bline.py <args>' to -# start the program. 'python bline.py <args>' seems OK. It's a Python -# installation problem. Don't know when all of this started. Dec 2018 was -# fine. Feb 2019 is not. It might be an older bug that has shown back up. -argv2 = [] -Index = 0 -for Arg in argv: - argv[Index] = argv[Index].strip() - if len(argv[Index]) != 0: - argv2.append(argv[Index]) - Index += 1 -argv = argv2 -# Just always try to get these two. If they end up being needed, fine. If not, -# that's fine too. -try: -# .upper in case letters are ever in there. - TagID = argv[1].upper() -# They are on the label, but I don't think any software uses them. - while TagID.startswith("0"): - TagID = TagID[1:] -except: - TagID = "" -try: - IPAddr = argv[2] -except: - IPAddr = "" -# Gotta have at least one. Fool the code below. -if len(argv) == 1: - argv.append("-h") -# The arguments that can be anywhere and do something and quit. -if "-#" in argv: - stdout.write("%s\n"%PROG_VERSION) - exit(0) -# LATER. -# We don't even have to go to main() with this one. -#if "-A" in argv: -# stdout.write("Looking for a baler. Press the ATTN button...\n") -# stdout.write("(Ctrl-C to stop before a baler is found.)\n") -# Ret = address_balers() -# stdout.write(Ret) -# exit(0) -if "-h" in argv: - stdout.write( \ - "Usage: bline.py <TagID> <IP address> <Command>\n") - stdout.write( \ - " -c = Checks communication with the baler and gets basic information.\n") - stdout.write( \ - " -L = Saves and displays the list of files on the baler.\n") - stdout.write("\n") - stdout.write( \ - "bline.py <TagID> <IP address> <Command> [<file(s)>]\n") - stdout.write( \ - " -O (Big O) = Offloads all data files that have not been offloaded.\n") - stdout.write( \ - " -o (Little O) = Offloads low sample rate data files that have not been\n") - stdout.write(" offloaded.\n") - stdout.write( \ - " -v = Gets the baler's list of files and checks the offloaded files.\n") - stdout.write( \ - " -vl = Gets the baler's list of files and checks the offloaded files.\n") - stdout.write( \ - " This also lists the files that have not been offloaded.\n") - stdout.write( \ - " -V = Examines the specified offloaded files for bad blocks. The\n") - stdout.write( \ - " <IP address> may be omitted.\n") - stdout.write("\n") - stdout.write( \ - "bline.py <TagID> <IP address> <Command> <file(s)>\n") - stdout.write( \ - " -e = Excludes the specified file(s) during an offload, otherwise\n") - stdout.write( \ - " low sample rate files will be offloaded (like -o).\n") - stdout.write( \ - " -E = Excludes the specified file(s) during an offload, otherwise\n") - stdout.write( \ - " all files will be offloaded (like -O).\n") - stdout.write( \ - " -F = Offloads only the specified file(s).\n") - stdout.write("\n") - stdout.write( \ - "bline.py <TagID> [<IP address>] <Command>\n") - stdout.write( \ - " -m = Follow the -m with a message.\n") - stdout.write("\n") - stdout.write( \ - "bline.py [<TagID> <IP address>] <Command>\n") -# LATER. Here and in the -H section. -# stdout.write( \ -# " -A = Watch for a baler to assign an IP address to.\n") - stdout.write( \ - " -h = This help.\n") - stdout.write( \ - " -H = More help.\n") - stdout.write( \ - " -i = Reports what may be the control computer's IP address and.\n") - stdout.write(" other information.\n") - stdout.write( \ - " -U = Checks for a newer program version at PASSCAL if connected\n") - stdout.write( \ - " to the Internet (the program will eventually be distributed\n") - stdout.write( \ - " using git and this will go away.\n") - stdout.write( \ - " -UD = Downloads most recent version from PASSCAL (try -U first).\n") - stdout.write("\n") - stdout.write( \ - "<TagID> = The tag ID on the front of the baler.\n") - stdout.write( \ - "<IP address> = The IP address assigned to the baler by BaleAddr.\n") - stdout.write("\n") - stdout.write( \ - "-E/-e commands are, for example, for excluding \"problem\" files that\n") - stdout.write( \ - " may be stopping the offload process.\n") - stdout.write( \ - "-F files that are not found on the baler will be created with HTML\n") - stdout.write( \ - " code in them saying the file doesn't exist (~100 bytes size).\n") - stdout.write( \ - "*, ?, [] UNIX file wildcards may be used in <file(s)>.\n") - stdout.write( \ - "On most systems you will need to enclose file names using wildcards\n") - stdout.write( \ - " with quotes like \"*.VER\" \"DT0001*\"\n") - stdout.write( \ - "Only one command per run.\n") - stdout.write( \ - "Always leave a space after the command line switches.\n") - stdout.write("\n\a") - exit(0) -if "-H" in argv: - HELPText = "USING BLINE.PY\n\ ---------------\n\ -BLINE offloads data files from a Quantera B14 Baler. It does not\n\ -set up the baler for offloading. BaleAddr is the Quanterra\n\ -Windows/Wine program that you start after you've hooked up the baler\n\ -to the laptop and to power. In it you set the IP address that you want\n\ -the baler to be set to when the baler starts up. If, for example, the\n\ -laptop IP address is something like 129.138.26.1, you would want the\n\ -baler's address to end up something like 129.138.26.2. Same sub-net,\n\ -etc. You set the desired IP address in BaleAddr then poke the ATTN button\n\ -on the baler. When the baler gets going BaleAddr handshakes and sets the IP\n\ -address.\n\ -\n\ -You can use\n\ -\n\ - bline.py -i\n\ -\n\ -to see what the IP address of the computer is set to. It might be\n\ -correct. It won't be correct if the Wireless is on, so turn that off.\n\ -Use the operating system's network setting widget to double check what\n\ -the address is if the address from the -i command does not seem to be\n\ -working.\n\ -\n\ -Depending on the operating system of the computer you are going to run\n\ -BaleAddr and BLINE on, you may have to manually set an IP address\n\ -for the computer. Some operating systems do not set their IP address\n\ -until they are connected to a network, and in some cases there is no\n\ -network until the baler wakes up from an ATTN button push. By then it\n\ -is too late.\n\ -\n\ -When everything works you should get the messages\n\ -\n\ -Setting time in baler\n\ -Baler OK\n\ -\n\ -in BaleAddr. If the 'Baler OK' message doesn't show up then the\n\ -program is not really talking to the baler/the baler's IP address did\n\ -not get set. It happens a lot. It seems to be a bug in the baler\n\ -firmware. Quiting/Resetting BaleAddr, re-poking the ATTN button on the\n\ -baler to let the baler shut down, restarting BaleAddr (if it was Quit),\n\ -and poking the ATTN button again up to five times has been required to\n\ -get the program and the baler to connect. It's just flakey. If you get\n\ -up to maybe three cycles you can try shutting down the baler and then\n\ -disconnecting the power and reconnecting, and then try again to get\n\ -things going. Sometimes that helps. Make sure you have a suitable IP\n\ -address in BaleAddr that is compatible with the address the computer\n\ -thinks it has. That REALLY helps. :) Also make sure the Wireless is\n\ -turned off like you have to do with EzBaler. None of the programs can\n\ -figure out which Ethernet adapter to use when there is more than one\n\ -active.\n\ -\n\ -BLINE is just a command line program, so once BaleAddr has connected to\n\ -the baler you start a Terminal/xterm/whatever window, cd into the\n\ -directory where you want a folder of data from the baler to be saved to,\n\ -like a \"DATA\" directory, and enter\n\ -\n\ - bline.py\n\ -or\n\ - <path to bline>/bline.py\n\ -or\n\ - ./bline.py\n\ -\n\ -The command to start the program depends on how BLINE was installed. Using\n\ -no command line arguments will list all of the commands for the program.\n\ -For basic offloading of everything on the baler you just enter\n\ -\n\ - bline.py <TagID> <IP address> -O\n\ -\n\ -<TagID> is the serial number on the baler, like 6003.\n\ -<IP address> is the address assigned to the baler using BaleAddr.\n\ -\n\ -Before offloading starts BLINE will check to see if any files are going\n\ -to be overwritten. If so a confirmation message will appear.\n\ -\n\ -Offloading should start. A sub-directory like 6003.sdr will be created\n\ -and the data files from the baler will be in there. The file 6003.msg\n\ -will be created in the directory in which BLINE was started. It will\n\ -have a copy of the messages that get written to the terminal window as\n\ -the program is working. There will be a 6003files.txt. This will be a\n\ -list of all of the data files that were found on the baler.\n\ -\n\ -When BLINE is finished with the baler and all of the data files have been\n\ -offloaded you can bring the baler up in BaleAddr again (assuming it's\n\ -like days later and the baler has shut down) and use the Clean Baler\n\ -button to erase everything and get the baler ready to go back out to\n\ -the field. Please insure you have ALL of the data files, and that they\n\ -are backed up, because unlike EzBaler, BaleAddr completly erases the\n\ -old data.\n\ -\n\ -INSTALLING\n\ -\n\ -To install BLINE copy the file bline.py to where ever you want. If you\n\ -put it somewhere like /opt/passcal/bin/ (the preferred choice if the\n\ -PASSCAL software package has been installed), with the other PASSCAL\n\ -software, then it should be in the path set up by the PASSCAL software\n\ -installation, and just \"bline.py\" will be used to get it going.\n\ -This command will probably have to be used\n\ -\n\ - sudo cp bline.py /opt/passcal/bin\n\ -\n\ -and the login password given to get it there. It will also probably\n\ -have to made executable with something like\n\ -\n\ - chmod +x bline.py\n\ -\n\ -Depending on where it is copied sudo may also need to be used before\n\ -chmod. If it is not executable a 'permission denied' message will be\n\ -displayed when trying to start the program.\n\ -\n\ -Installing it to any other location will require ensuring that the location\n\ -is in the current path and the necessary entries have been made in files\n\ -such as .bashrc, or .bash_profile, or any other initialization files for\n\ -the command shell in use.\n\ -\n\ -COMMAND DETAILS\n\ -\n\ -BLINE is designed to be run in or started from the directory where you want\n\ -to work. You cannot use paths to directories. The current working directory\n\ -or the \"work directory\" must be set by changing to the directory of\n\ -choice using the command line with something like the 'cd' command before\n\ -starting the program. Files created by the program will be written the the\n\ -work directory and offloaded files will be written to a sub-directory of\n\ -the work directory (<TagID>.sdr). Verification commands, -v/-vl and -V,\n\ -expect the program to be started in the same directory where it was\n\ -running when the files were offloaded from the baler, if they have not\n\ -been moved since offloading them.\n\ -\n\ -All of the commands are placed on the command line:\n\ -\n\ - bline.py <TagID> <IP address> <command> <file(s)>\n\ -\n\ -See the -h help for the command line items that are required or optional\n\ -for each command.\n\ -\n\ -The <TagID> is used as part of the bookkeeping file names and the directory\n\ -name where the offloaded data files will be placed. It is also used by\n\ -BLINE to make sure it is talking to the baler the user thinks it is\n\ -talking to. The <IP address> is used for the Ethernet communications\n\ -protocol to allow the program to actually communicate with the baler. It\n\ -is the address that was set using BaleAddr.\n\ -\n\ -The -V command does not communicate with the baler, so the IP address of\n\ -the baler is not used, and does not need to be included.\n\ -\n\ -Some commands allow a space-separated list of file names with or without\n\ -standard UNIX wildcard characters to follow them to control which data\n\ -files are delt with. This may, for example, be a list of files to offload,\n\ -or it may be a list of files to examine. It depends on the command. On most\n\ -systems you will need to enclose the file names in double quotes when using\n\ -wildcard characters, like \"*\", instead of just *. In most cases not\n\ -supplying any file list is the same as using \"*\", i.e. 'all files'.\n\ -\n\ --c\n\ -This command uses the <TagID> and <IP address> to simply establish a\n\ -connection with the spcified baler at the specified IP address. Error\n\ -messages will be displayed if anything goes wrong.\n\ -\n\ --E\n\ -The -E command is basically the same as -O, except that it allows specified\n\ -files (in the <file(s)> list) to be excluded from offloading. If a baler\n\ -offload session always fails on a specific file then the -E command and\n\ -that file's name could be specified, so that it will be skipped and the\n\ -rest of the files will be offloaded.\n\ -\n\ --e\n\ --e is the same as -E, but it will only try to offload low sample rate files\n\ -that are not in the list of files to exclude.\n\ -\n\ --F\n\ -Only the file(s) specified in the [<file(s)>] argument will be offloaded.\n\ -This could be used for a different form of only offloading the low sample\n\ -rate data files. A command like\n\ -\n\ - bline.py 6003 123.123.123.4 -F \"DT0001*\"\n\ -\n\ -would offload the first file of each channel, which would also include a\n\ -little bit of the high sample rate data. For short amounts of baler data\n\ -this would be OK, but channels of low sample rate, or SOH, data would not\n\ -be offloaded if there were more than one DT-file for a channel. The -o\n\ -could be used and then this -F command example.\n\ -\n\ --h and -H\n\ -The -h command prints a summary list of possible command line arguments\n\ -to the display. The -H command prints a more detailed help document to the\n\ -display.\n\ -\n\ --i\n\ -This command asks the computer running BLINE to print its IP address.\n\ -This address may be used to help select an IP address to set the baler to\n\ -in the BaleAddr program. The address printed may or may not be correct or\n\ -useful. Some operating systems will not print the correct address until\n\ -the computer is already talking to a baler. Some operating system will\n\ -report the wrong address if both the wired and wireless Ethernet systems\n\ -are in use. The list goes on.\n\ -\n\ -The most reliable way to determine the IP address of the computer for\n\ -running BLINE is to look in the system preferences for the operating\n\ -system in use.\n\ -\n\ --L\n\ -This command simply contacts the baler, requests the list of data files\n\ -the baler thinks are available, and saves that list to the file\n\ -\n\ - <TagID>files.txt\n\ -\n\ --m\n\ -Follow the -m with a message that will be displayed, but also written to\n\ -the .msg file for the baler. The text may need to be enclosed in quotes:\n\ - -m \"Baler 5549 is station NUUK\".\n\ -\n\ --O (Big O)\n\ -This command retrieves the list of files on the baler from the baler, and\n\ -then requests all of them for offloading from the baler. The files are\n\ -placed in the sub-directory <TagID>.sdr. Additional [<file(s)>] may be\n\ -listed to control offloading a subset of the available files on the baler.\n\ -\n\ -Files in the <TagID>.sdr directory that appear to have already been\n\ -offloaded (file name and size match a file on the baler) will not be\n\ -offloaded again.\n\ -\n\ --o (small o)\n\ -This command retrieves the list of files on the baler from the baler, and\n\ -then requests all of the the files whose channel name, like .HHZ, does\n\ -not start with the letter H or S (as specified in the SEED manual as being\n\ -'high speed' channel names). The files are placed in the sub-directory\n\ -<TagID>.sdr. Additional [<file(s)>] may be listed to control offloading a\n\ -subset of the available files on the baler.\n\ -\n\ --U, -UD\n\ -If the computer is connected to the Internet running\n\ -\n\ - bline.py -U\n\ -\n\ -will check to see if there is a newer version of the program than the one\n\ -running on the computer. If there is a newer version use the -UD command to\n\ -obtain it. Unzip the file that is downloaded and install the new version\n\ -from there (see other instructions).\n\ -\n\ -Eventually updates to the program will be obtained by downloading a new\n\ -version using 'git' and -U and -UD will go away.\n\ -\n\ --v (little V) and -vl\n\ -The -v/-vl commands get the list of files from the baler (which would be\n\ -files.htm if you were talking to the baler with a browser) and then looks\n\ -to see if all of the files on that list are in the <TagID>.sdr directory\n\ -on the computer and if they are the same size as they are in the list. The\n\ -function does not do any checksum checking, because there's no way to get\n\ -the baler to compute a checksum value of the files on the baler to verify\n\ -against. A <file(s)> list may be supplied if only specified files are to\n\ -be verified. The -vl command additionally lists the files that have not\n\ -been offloaded from the baler.\n\ -\n\ --V (big V)\n\ -The -V command reads block-by-block through the offloaded data file(s)\n\ -in the <TagID>.sdr directory for the specified baler and collects and\n\ -reports:\n\ -\n\ - 1. The earliest block header time in a file\n\ - 2. The latest block header time in a file\n\ - 3. The number of blocks read, and the block size in a file\n\ - 4. A list of all of the station IDs in a file (should normally only\n\ - be one)\n\ - 5. A list of all of the channel names in a file (should normally only\n\ - be one)\n\ -\n\ -For baler data files the normal block size is 4096 bytes and that is\n\ -normally 4100 blocks per 16MB data file.\n\ -\n\ -Things to look for after the function runs are bad/scrambled/missing\n\ -dates and times, multiple station names, multiple channel names and the\n\ -program crashing. The latest block time will not normally be the same\n\ -as the last sample time for each channel. For quiet and low sample rate\n\ -channels the time may be quite a bit earlier than the last sample time.\n\ -\n\ -A summary of information is printed after all of the files have been\n\ -examined.\n" - stdout.write("\n") - stdout.write(HELPText) - stdout.write("\n") - exit(0) -if "-i" in argv: - import socket - stdout.write("Contol computer IP address (maybe): %s\n"% \ - socket.gethostbyname(socket.gethostname())) - stdout.write("Python version: %s\n"%PROG_PYVERSION) - stdout.write("\n") - exit(0) -######################## -# BEGIN: args2Str(Start) -# FUNC:args2Str():2017.163 -# Just returns a string version of the command line. -def args2Str(Start): - Return = "" - for arg in argv[Start:]: - Return += " "+arg + +########################################## +# BEGIN: args2SL(SorL, Start, Delim = " ") +# FUNC:args2SL():2017.233 +# Constructs a list or string of the command line arguments starting at the +# argv index position if Start is an int, or after the command line argument +# that matches Start if Start is a string. +def args2SL(SorL, Start, Delim = " "): + if SorL == "s": + Return = "" + elif SorL == "l": + Return = [] + if isinstance(Start, astring): + FoundArg = False + for Arg in argv: + if Arg == Start: + FoundArg = True + continue + if FoundArg == True: + if SorL == "s": + Return += "%s%s"%(Arg, Delim) + if SorL == "l": + Return.append(Arg) + elif isinstance(Start, anint): +# The caller may ask for args that are not there. + try: + for Arg in argv[Start:]: + if SorL == "s": + Return += "%s%s"%(Arg, Delim) + if SorL == "l": + Return.append(Arg) + except: + pass + if SorL == "s" and Delim != "": + if Return.endswith(Delim): + Return = Return[:-len(Delim)] return Return -# END: args2Str +# END: args2SL + + +###################### +# BEGIN GROUP: bader() +# FUNCS:bader():2019.268 +# A collection of functions from the bader.py program. +# Gives Quanterra Baler-14 units an IP address based on the computer's IP. +# Lloyd Carothers, IRIS/PASSCAL +# Based on bader.py, 2019.193. +from calendar import timegm +from glob import glob +import socket +import struct +##################### +# BEGIN: logging(Msg) +# FUNC:logging():2019.235 +# Instead of the logging module. +def logging(Msg): + if 1 == 2: + print(Msg) + return +# END: logging ########################## -# BEGIN: getArgvFiles(Cmd) -# FUNC:getArgvFiles():2019.019 -# Reads any arguments after the passed command. If they are files then fine -# and if they are it's not my fault. -from copy import deepcopy +# BEGIN CLASS: CRC(object) +# CLASS:CRC(object):2019.235 +class CRC(object): + TABLE = None + ############ + @classmethod + def create_table(cls): + cls.TABLE = [None]*256 + tdata = 0 + accum = 0 + F = 0xFFFFFFFF + for count in range(0, 256): + tdata = (count << 24) + accum = 0 + for bits in range(1, 9): + if (tdata ^ accum) & 2**31: + accum = (accum << 1) ^ 1443300200 + accum = accum & F + else: + accum = (accum << 1) & F + tdata = (tdata << 1) & F + cls.TABLE[count] = accum + return + ############ + @classmethod + def compute_crc(cls, buf): + if not cls.TABLE: + cls.create_table() + lng = 0 + firstByte = 0 + F = 0xFFFFFFFF + dataPtr = 0 + dataLen = len(buf) + while dataLen > 0: + if PROG_PYVERS == 2: + thisByte = ord(buf[dataPtr]) + elif PROG_PYVERS == 3: + thisByte = buf[dataPtr] + newCrc = F & (lng << 8) ^ cls.TABLE[(firstByte ^ thisByte) & 255] + firstByte = newCrc >> 24 + lng = newCrc + dataPtr += 1 + dataLen -= 1 + return newCrc +# END CLASS: CRC + +############################# +# BEGIN CLASS: Packet(object) +# CLASS:Packet():2019.235 +class Packet(object): + header_struct = struct.Struct('!LBBHHH') + assert header_struct.size == 12 + brdy_cmd_code = 0x5A + brdy_struct = struct.Struct('!LL2s5sBHHLQ') + assert brdy_struct.size == 32 + vack_struct = struct.Struct('!5L6HQ') + assert vack_struct.size == 40 + baler_response_code = 0x5C + baler_response_struct = struct.Struct('!HHL') + assert baler_response_struct.size == 8 + seq_counter = 666 + #################### + def crc_check(self): + if self.crc == CRC.compute_crc(self.buf[4:]): + logging('crc: pass') + return True + else: + logging('crc: fail') + return False + ###################### + def unpack(self, buf): + self.buf = buf + self.unpack_header(buf[:self.header_struct.size]) + self.crc_check() + if self.command == self.brdy_cmd_code: + self.unpack_brdy(buf[self.header_struct.size:]) + return + ############################# + def unpack_header(self, buf): + (self.crc, + self.command, + self.proto_version, + self.length, + self.seq_number, + self.ack_number) = self.header_struct.unpack(buf) + return + ########################### + def unpack_brdy(self, buf): + (self.q330_sn1, + self.q330_sn2, + self.net, + self.station_name, + self.flags, + self.model_num, + self.baler_version, + self.disk_size, + self.baler_sn) = self.brdy_struct.unpack(buf) + return + ########################################### + def pack_vack(self, ip, netmask, brdy_pkt): + assert isinstance(brdy_pkt, self.__class__) + self.q330_sn1 = 0 + self.q330_sn2 = brdy_pkt.q330_sn2 + self.q330_ip = 0 + self.baler_ip = ip + self.baler_netmask = str(netmask) + self.q330_base_port = 0 + self.q330_data_port = 0 + self.bps = 0 + self.flags = 0 + self.access_timeout = 0 + self.serial_baud_rate = 0 + self.baler_sn = brdy_pkt.baler_sn + self.payload = self.vack_struct.pack( + self.q330_sn1, + self.q330_sn2, + self.q330_ip, + struct.unpack( '!I', socket.inet_aton( self.baler_ip))[0], + struct.unpack( '!I', socket.inet_aton( self.baler_netmask))[0], + self.q330_base_port, + self.q330_data_port, + self.bps, + self.flags, + self.access_timeout, + self.serial_baud_rate, + self.baler_sn) + # Header. C7=baler command. + self.command = 0xC7 + self.proto_version = brdy_pkt.proto_version + self.ack_number = brdy_pkt.seq_number + self.pack_header() + return + #################### + def pack_ping(self): + ''' + An empty command packet useful to ping the baler. + ''' + self.baler_command = 0 + self.sequencing_field = 0 + self.flags = 0 + clean_struct = struct.Struct('!2HL') + self.payload = clean_struct.pack(self.baler_command, \ + self.sequencing_field, self.flags) + # Header. C8=baler command. + self.command = 0xC8 + self.proto_version = 2 + self.ack_number = 0 + self.pack_header() + return + ######################################## + def pack_set_baler_time(self, brdy_pkt): + base_time = timegm( (2000, 1, 1, 0, 0, 0, 0, 0, 0) ) + now = time() - base_time + self.baler_command = 5 + self.sequencing_field = 0 # used for multiple packet cmds + self.time = int(now) + time_struct = struct.Struct('!2HL') + self.payload = time_struct.pack(self.baler_command, \ + self.sequencing_field, self.time) + # Header. C8=baler command. + self.command = 0xC8 + self.proto_version = brdy_pkt.proto_version + self.ack_number = 0 + self.pack_header() + return + ######################## + def pack_shutdown(self): + ''' + Turn baler off qlib calls dealocate + ''' + self.baler_command = 4 + self.sequencing_field = 0 + self.flags = 0 + clean_struct = struct.Struct('!H') + self.payload = clean_struct.pack(self.baler_command) + # Header. C8=baler command. + self.command = 0xC8 + self.proto_version = 2 + self.ack_number = 0 + self.pack_header() + return + ##################### + def pack_clean(self): + ''' + Clean the baler and remove q330 association + ''' + self.baler_command = 2 + self.sequencing_field = 0 + self.flags = 0xFF + clean_struct = struct.Struct('!3H') + self.payload = clean_struct.pack(self.baler_command, \ + self.sequencing_field, self.flags) + # Header. C8=baler command. + self.command = 0xC8 + self.proto_version = 2 + self.ack_number = 0 + self.pack_header() + return + ###################### + def pack_header(self): + self.crc = 0 + self.length = len(self.payload) + self.seq_counter += 1 + header_bytes = self.header_struct.pack( + self.crc, + self.command, + self.proto_version, + self.length, + self.seq_counter, + self.ack_number) + qdp_packet = header_bytes + self.payload + # Compute and insert CRC. + to_crc = qdp_packet[4:] + self.crc = CRC.compute_crc(to_crc) + self.buf = struct.pack('!L', self.crc) + to_crc + return + ################## + def __str__(self): + ret = 'QDP packet:' + for key, value in self.__dict__.items(): + if key in ('buf', 'payload'): + continue + ret += "\n%s:\t%s\n"%(key[:20], value) + return ret +# END CLASS: Packet + +if not hasattr(socket, 'IP_PKTINFO'): + if PROGSystem == "dar": + socket.IP_PKTINFO = 26 + if PROGSystem == "lin": + socket.IP_PKTINFO = 8 + if PROGSystem == "win": + socket.IP_PKTINFO = 8 + +######################### +# BEGIN: socketComm(Host) +# FUNC:socketComm():2019.255 +def socketComm(Host): + Sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + Sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + Sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + Sock.settimeout(2) + Sock.bind((str(Host), 0)) + return Sock +# END: socketComm -def getArgvFiles(Cmd): - FileList = [] - FoundCmd = False - for arg in argv: - if arg == Cmd: - FoundCmd = True +########################## +# BEGIN: ipFromDevice(Dev) +# FUNC:ipFromDevice():2019.267 +def ipFromDevice(Dev): + try: + addresses = [ad for ad in net_if_addrs()[Dev] + if ad.family is socket.AF_INET] + except KeyError: + return (1, "RW", "Is device '%s' on this system?"%Dev, 2) + if len(addresses) == 0: + logging("Dev:%s no address."%Dev) + return (1, "", "'%s' has no IP address."%Dev) + elif len(addresses) > 1: + logging("Dev:%s doesn't have a single IP:%s"%(Dev, addresses)) + return (1, "", "'%s' has %d addresses."%(Dev, len(addresses))) + a = addresses[0] + if PROG_PYVERS == 2: + aaddress = unicode(a.address) + anetmask = unicode(a.netmask) + net = IPv4Network((aaddress, anetmask), strict=False) + ip_if = ip_interface((aaddress, net.prefixlen)) + elif PROG_PYVERS == 3: + net = IPv4Network((a.address, a.netmask), strict=False) + ip_if = ip_interface((a.address, net.prefixlen)) + return (0, ip_if) +# END: ipFromDevice + +############################################################# +# BEGIN: addressABaler(SETspec, MSGspec, TagID, IEthDev = "") +# FUNC:addressABaler():2019.268 +# Providing the Ethernet device overrides watching them all and cuts out +# using recvmsg(). +def addressABaler(SETspec, MSGspec, TagID, IEthDev = ""): +# This is about all we can check. + try: + TagID = int(TagID) + except: + return (1, "RW", "Bad TagID: '%s'"%TagID) +# A socket for listening for baler ready packets. +# Setting Host to "" tries on any address and any interface. + Host = "" + Port = 65535 + Sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + Sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + Sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +# Only needed if searching. + if IEthDev == "": + try: + Sock.setsockopt(socket.IPPROTO_IP, socket.IP_PKTINFO, 1) +# It's different exceptions in different places. Just catch them all. + except: + return (1, "MW", \ + "The Ethernet device name must be supplied on this system.", \ + 3) + Sock.bind((Host, Port)) + Tries = 0 + while True: + if IEthDev == "": +# The user may not know about this. + try: + Raw, Msgs, Flags, Addr = Sock.recvmsg(44, 128) + except AttributeError: + return (1, "MW", \ + "The Ethernet device name must be supplied on this system.", \ + 3) + logging("%s %s %s %s"%(Raw, Msgs, Flags, Addr)) + BalerPort = Addr[1] +# Figure out which Ethernet device the baler is on. + EthDev = None + for msg in Msgs: + if msg[1] == socket.IP_PKTINFO: + if_index = struct.unpack('I8B', msg[2])[0] + EthDev = socket.if_indextoname(if_index) + break + if EthDev is None: + return (1, "MW", "Could not determine Ethernet device.", 3) + elif IEthDev != "": + EthDev = IEthDev + Raw, Addr = Sock.recvfrom(128) + BalerPort = Addr[1] + logging("Received baler ready packet from %s on dev:%s"%(Addr, \ + EthDev)) + brdy_pkt = Packet() + brdy_pkt.unpack(Raw) + logging(brdy_pkt) + if TagID != brdy_pkt.q330_sn2: + logIt(MSGspec, "Found baler %s. Ignoring."%brdy_pkt.q330_sn2) + Tries += 1 + if Tries == 3: + return (1, "RW", "Did not see baler %d."%TagID, 2) continue - if FoundCmd == True: - FileList.append(arg.upper()) - if len(FileList) == 0: - FileList.append("*") - return FileList -# END: getArgvFiles - -# The more complicated commands. -CmdReady = False -if CmdReady == False and "-V" in argv: - ArgBadBlocks = True - Ret = getArgvFiles("-V") - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-U" in argv: - ArgUCheck = True - CmdReady = True -if CmdReady == False and "-UD" in argv: - ArgUDown = True - CmdReady = True -if CmdReady == False and "-c" in argv: - ArgCheck = True - CmdReady = True -if CmdReady == False and "-L" in argv: - ArgList = True - CmdReady = True -if CmdReady == False and "-v" in argv: - ArgVerify = True - Ret = getArgvFiles("-v") - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-vl" in argv: - ArgVerify = True - ArgVerifyList = True - Ret = getArgvFiles("-v") - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-O" in argv: - Ret = getArgvFiles("-O") - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-o" in argv: - ArgLSROffload = True - Ret = getArgvFiles("-o") - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-e" in argv: - ArgSkipo = True - Ret = getArgvFiles("-e") - if Ret == ["*"]: - stdout.write("No -e files specified.\n\a") - exit(1) - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-E" in argv: - ArgSkipO = True - Ret = getArgvFiles("-E") - if Ret == ["*"]: - stdout.write("No -E files specified.\n\a") - exit(1) - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-F" in argv: - ArgSpec = True - Ret = getArgvFiles("-F") - if Ret == ["*"]: - stdout.write("No -F files specified.\n\a") - exit(1) - ArgSpecFiles = deepcopy(Ret) - CmdReady = True -if CmdReady == False and "-m" in argv: - Index = argv.index("-m") -# You have to at least supply the TagID so we know which file to write to. - if Index == 1: - stdout.write("You must at least supply TagID before -m.\n\a") - exit(1) - TheMessage = "" - for I in arange(Index+1, len(argv)): - TheMessage += argv[I]+" " - if TheMessage == "": - stdout.write("No -m message entered.\n\a") - exit(1) - WriteMsg = True - CmdReady = True -if CmdReady == False: - stdout.write("What??\n\n") - exit(1) + logIt(MSGspec, "Baler %s found..."%brdy_pkt.q330_sn2) + Ret = ipFromDevice(EthDev) + if Ret[0] != 0: + return Ret + localhost = Ret[1] +# Was nextFreeIP(). + IP = 0 +# Don't go on forever looking for an address. Make them move to a quieter +# neighborhood. Also, the baler will shutdown if it takes too long. + Count = 0 + for IP in (I for I in localhost.network.hosts() if I is not Host): + IP = str(IP) + try: +# Of course the commands are different. + if PROGSystem in ("dar", "lin"): + Cp = sb.run(["ping", "-c1", IP], timeout = .3, \ + stdout = sb.PIPE, stderr = sb.PIPE) + elif PROGSystem == "win": + Cp = sb.run(["ping", IP, "-n", "1"], timeout = .3, \ + stdout = sb.PIPE, stderr = sb.PIPE) + Count += 1 + if Count > 100: + return (1, "MW", "No free address found in 100 tries.", 3) + except sb.TimeoutExpired: + break + if IP == 0: + return (1, "MW", "ERROR: Could not determine IP address.", 3) + BalerIP = str(IP) + vack = Packet() + vack.pack_vack(BalerIP, localhost.netmask, brdy_pkt) + logging(vack) + vack.crc_check() + SockComm = socketComm(localhost.ip) + BytesSent = SockComm.sendto(vack.buf, ('255.255.255.255', BalerPort)) +# It looks like the computer can get ahead of the baler and send commands too +# quickly before the baler has finished doing something and is ready to +# respond to a sent command (e.g. time and ping). This pause for sure improved +# things on Macs. There are still random response failures, but it looks like +# the IP setting still happens. When something like this fails with BaleAddr +# you still can't talk to the baler. + sleep(1.0) + logging("Sent vack %d of %d"%(BytesSent, len(vack.buf))) + Maybe = False +# Set the time. + logIt(MSGspec, "Setting time in baler...") + time_pkt = Packet() + time_pkt.pack_set_baler_time(brdy_pkt) + BytesSent = SockComm.sendto(time_pkt.buf, (BalerIP, BalerPort)) + sleep(1.0) + try: + Raw, Addr = SockComm.recvfrom(14) + time_ack_pkt = Packet() + time_ack_pkt.unpack(Raw) + logging(time_ack_pkt) + logIt(MSGspec, "Baler %s time set."%brdy_pkt.q330_sn2) + except Exception as e: + logIt(MSGspec, "Did not receive time command response.") + logging("%s"%e) + Maybe = True +# Test the connection by pinging the baler. + ping = Packet() + ping.pack_ping() + BytesSent = SockComm.sendto(ping.buf, (BalerIP, BalerPort)) + logging("Sent ping %d of %d"%(BytesSent, len(ping.buf))) + sleep(1.0) + try: + Raw, Addr = SockComm.recvfrom(14) + ack_pkt = Packet() + ack_pkt.unpack(Raw) + logging(ack_pkt) + logIt(MSGspec, "Ping response received.") + logIt(MSGspec, "Baler OK.") + except Exception as e: + logIt(MSGspec, "Did not receive ping response from baler.") + logging("%s"%e) + Maybe = True + Ret = getSetSettings(SETspec, "set", EthDev, BalerIP, BalerPort) +# &&&&& For now. + if Ret[0] != 0: + Maybe = True +# Now that we know what we are talking on show the laptop stats. + Ret = ipFromDevice(EthDev) + if Ret[0] == 0: + CCIPMask = Ret[1] + CCIP, CCMask = ("%s"%CCIPMask).split("/") + logIt(MSGspec, "Control computer IP address: %s"%CCIP) + logIt(MSGspec, "Control computer netmask: %s"% \ + bitsToNetmask(CCMask)) + elif Ret[0] != 0: +# Just keep going. Sometimes it all still works out. + logIt(MSGspec, "%s"%Ret[2]) + Msg = "%s set to %s, port %s, on %s"%(brdy_pkt.q330_sn2, \ + BalerIP, BalerPort, EthDev) + if Maybe == True: + Msg += " (maybe)" + return (0, "GB", Msg, 1) +# END: addressABaler + +########################################### +# BEGIN: cleanBaler(EDevice, baler_ip_port) +# FUNC:cleanBaler():2019.268 +def cleanBaler(EDevice, baler_ip_port): + Ret = ipFromDevice(EDevice) + if Ret[0] != 0: + return Ret + localhost = Ret[1] + clean_p = Packet() + clean_p.pack_clean() + SockComm = socketComm(localhost.ip) + BytesSent = SockComm.sendto(clean_p.buf, baler_ip_port) + logging("Sent clean %d of %d"%(BytesSent, len(clean_p.buf))) + logIt(MSGspec, "Clean command sent...") + logIt(MSGspec, " Waiting...", False) + SockComm.settimeout(20) + try: + while 1: + try: + Raw, Addr = SockComm.recvfrom(75) + break + except socket.timeout: + logIt("", " Waiting...", False) + except KeyboardInterrupt: + return (1, "RW", "Stopped by user. Baler may not be finished.", 2) + return (0,) +# END: cleanBaler + +DevFormat = "/proc/sys/net/ipv4/conf/%s/rp_filter" + +######################## +# BEGIN: rpFilterOK(dev) +# FUNC:rpFilterOK(dev):2019.255 +def rpFilterOK(dev): +# dev = DevFormat.format(dev) + dev = DevFormat%dev + with open(dev, 'r') as pf: + status = int(pf.read()) + if status == 0: + return True + else: + logging("%s = %s"%(dev, status)) + return False +# END: rpFilterOK + +############################### +# BEGIN: checkRPFiltersOff(Dev) +# FUNC:checkRPFiltersOff():2019.268 +# The option value must be "off" to get baler ready broadcast packets. This +# tests the option for the passed device, or all devices. +# Dev is a network device, e.g. enp1s0 +# Dev=None checks all devices. +def checkRPFiltersOff(Dev): +# Same check as in -b command. + if PROGSystem != "lin": + logging('Platform not linux, should be fine.') + return (0,) + from pexpect import spawn + Devs = ["all"] +# Add the passed Dev or get a List of all the files we'll need to check. + if Dev != "": + Devs.append(Dev) + else: + DevPaths = glob('/proc/sys/net/ipv4/conf/*/rp_filter') + for DevPath in DevPaths: + Parts = DevPath.split("/") + Devs.append(Parts[-2]) + for Dev in Devs: + if rpFilterOK(Dev) == False: + stdout.write("The option filter for '%s' needs to be reset...\n"% \ + Dev) + DevPath = DevFormat%Dev + stdout.write("Executing: sudo bash -c \"echo 0 > %s\"\n"%DevPath) + stdout.write(" Enter the password") + stdout.flush() + Sp = spawn("sudo bash -c \"echo 0 > %s\""%DevPath) + Sp.timeout = 2 + Sp.expect("password") + Sp.interact() + if rpFilterOK(Dev) == False: + stdout.write("Still bad.\n") + else: + stdout.write("Fixed.\n") + return (0, any((rpFilterOK(Dev) for Dev in Devs))) +# END: checkRPFiltersOff +# END GROUP: bader + + + + +#################################################### +# BEGIN: badBlocks(MSGspec, SDRspec, IFiles, Filter) +# FUNC:badBlocks():2019.289 +def badBlocks(MSGspec, SDRspec, IFiles, Filter): + CLFiles = deepcopy(IFiles) + CCFiles = listdir(SDRspec) + CCFiles.sort() + FilesChecked = 0 + FilesOK = 0 + FilesOKSize = 0 +# These get loaded with the file names depending on the Ret[0] value coming +# from checkDTFile() for a summary. It's teh last number in the names. + FilesOpenErrors1 = [] + FilesTooSmall2 = [] + Files256Size3 = [] + FilesEndEmpty4 = [] + FilesNoData5 = [] + FilesMultStas6 = [] + FilesMultChans7 = [] + FilesIsDir8 = [] +# For an overall baler time range. + FirstFilesTime = "Z" + LastFilesTime = "" +# Loop through CCFiles and then through all of the file(s) the user specified +# looking for matches. + for Index in arange(0, len(CCFiles)): + CCFile = CCFiles[Index] +# Probably macOS. + if CCFile.startswith("."): + continue +# Don't process files that don't look like they came from a baler (like .ALL +# or .<chan> files). + if Filter == True: + if basename(CCFile).find("__.") == -1: + continue + Matches = False + for CLFile in CLFiles: + if fnmatch(CCFile, CLFile) == True: + Matches = True + break + if Matches == False: + continue + CCFilespec = SDRspec+CCFile +# checkDTFile() will also catch this. We'll still look for Rec[0]==8. Non-data +# files will probably come back with a 256Size error. + if isdir(CCFilespec): + continue + FilesChecked += 1 + CCFSize = getsize(CCFilespec) + logIt(MSGspec, " %d. Checking %s... (%s %s)"%(FilesChecked, \ + CCFilespec, fmti(CCFSize), sP(CCFSize, ("byte", "bytes"))), \ + False) + Ret = checkDTFile(CCFilespec, " ") + logIt(MSGspec, Ret[2], False) + FirstTime = Ret[4] + LastTime = Ret[5] +# If something goes wrong the returned times will be "". + if FirstTime != "" and FirstTime < FirstFilesTime: + FirstFilesTime = FirstTime + if LastTime != "" and LastTime > LastFilesTime: + LastFilesTime = LastTime + if Ret[0] == 0: + FilesOK += 1 + FilesOKSize += CCFSize + elif Ret[0] == 1: + FilesOpenErrors1.append(CCFile) + elif Ret[0] == 2: + FilesTooSmall2.append(CCFile) + elif Ret[0] == 3: + Files256Size3.append(CCFile) + elif Ret[0] == 4: + FilesEndEmpty4.append(CCFile) + elif Ret[0] == 5: + FilesNoData5.append(CCFile) + elif Ret[0] == 6: + FilesMultStas6.append(CCFile) + elif Ret[0] == 7: + FilesMultChans7.append(CCFile) + elif Ret[0] == 8: + FilesIsDir8.append(CCFile) + if FilesChecked == 0: + logIt(MSGspec, "There were no files to check.") + return + logIt(MSGspec, " Summary:", False) + logIt(MSGspec, " Overall date range: %s to %s"%(FirstFilesTime, \ + LastFilesTime), False) + logIt(MSGspec, " Files checked: %d"%FilesChecked, False) + logIt(MSGspec, " Files OK: %d (%s %s)"%(FilesOK, \ + fmti(FilesOKSize), sP(FilesOKSize, ("byte", "bytes"))), False) + if len(FilesOpenErrors1) != 0: + logIt(MSGspec, " File opening errors: %d:"%len(FilesOpenErrors1), \ + False) + Count = 0 + for File in FilesOpenErrors1: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + if len(FilesTooSmall2) != 0: + logIt(MSGspec, " Files too small: %d:"%len(FilesTooSmall2), False) + Count = 0 + for File in FilesTooSmall2: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + if len(Files256Size3) != 0: + logIt(MSGspec, " Size not /256 bytes: %d:"%len(Files256Size3), False) + Count = 0 + for File in Files256Size3: + Count += 1 + logIt(MSGspec, " %d. %s (%s bytes)"%(Count, File, \ + fmti(getsize(SDRspec+File))), False) + if len(FilesEndEmpty4) != 0: + logIt(MSGspec, " Ending empty: %d:"%len(FilesEndEmpty4), False) + Count = 0 + for File in FilesEndEmpty4: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + if len(FilesNoData5) != 0: + logIt(MSGspec, " No data: %d:"%len(FilesNoData5), False) + Count = 0 + for File in FilesNoData5: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + if len(FilesMultStas6) != 0: + logIt(MSGspec, " Multiple stations: %d:"%len(FilesMultStas6), False) + Count = 0 + for File in FilesMultStas6: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + if len(FilesMultChans7) != 0: + logIt(MSGspec, " Multiple channels: %d:"%len(FilesMultChans7), False) + Count = 0 + for File in FilesMultChans7: + Count += 1 + logIt(MSGspec, " %d. %s"%(Count, File), False) + elif len(FilesIsDir8) != 0: + logIt(MSGspec, " Directories: %d"%len(FilesIsDir8), False) + Count = 0 + for File in FilesIsDir8: + Count += 1 + logIt(MSGspec, " %d. %s\n"%(Count, File), False) + return +# END: badBlocks + + + + +############### +# BEGIN: beep() +# FUNC:beep():2019.227 +def beep(): + print("\a") +# END: beep + + + + +################################### +# BEGIN: def bitsToNetmask(NetBits) +# FUNC:bitsToNetmask():2019.211 +def bitsToNetmask(NetBits): + NetBits = int(NetBits) + Mask = (0xffffffff >> (32-NetBits)) << (32-NetBits) + return (str((0xff000000 & Mask) >> 24)+'.'+ \ + str((0x00ff0000 & Mask) >> 16)+'.'+ \ + str((0x0000ff00 & Mask) >> 8)+'.'+ \ + str((0x000000ff & Mask))) +# END: bitsToNetmask + + + + +########################################### +# BEGIN: checkDTFile(Filespec, Prefix = "") +# FUNC:checkDTFile():2019.253 +# Reads the mini-seed file block headers to see if they are corrupted. +# Note Ret[0] values, so the caller can keep tabs. Also the return value +# has the file timerange added to the end. +def checkDTFile(Filespec, Prefix = ""): +# Keep a list of station IDs that we come across. Should be just one. + StationIDs = [] +# Keep a list of the channel names we find (should be just one for balers). + Channels = [] +# The caller should be smarter than this...but maybe not. + if isdir(Filespec): + return (8, "RW", "%sFile %s is a directory."%(Prefix, \ + basename(Filespec)), 2, "", "") + try: + Fp = open(Filespec, "rb") + except Exception as e: + return (1, "MW", "%s%s"%(Prefix, str(e).strip()), 3, "", "") +# This one is easy. + if getsize(Filespec)%256.0 != 0.0: + return (3, "MW", "%sFILE NOT MULTIPLE OF 256 BYTES."%Prefix, 3, "", "") +# The standard record size for baler files is 4K. Read one and determine if +# it is smaller. + Record = Fp.read(4096+256) + RecordLen = len(Record) + if RecordLen == 0: + return (0, "GB", "%sFile empty."%Prefix, 0, "", "") + StaID = Record[8:13].decode("latin-1") + if StaID == Record[8+256:13+256].decode("latin-1"): + RecordSize = 256 + elif StaID == Record[8+512:13+512].decode("latin-1"): + RecordSize = 512 + elif StaID == Record[8+1024:13+1024].decode("latin-1"): + RecordSize = 1024 + elif StaID == Record[8+2048:13+2048].decode("latin-1"): + RecordSize = 2048 + elif StaID == Record[8+4096:13+4096].decode("latin-1"): + RecordSize = 4096 + else: + return (1, "MW", "%sCould not determine record size."%Prefix, 3, \ + "", "") + Fp.seek(0) +# Read the file in 10 record chunks to make it faster. + RecordsSize = RecordSize*10 + RecordsRead = 0 + FirstTime = "Z" + LastTime = "" + while 1: + Records = Fp.read(RecordsSize) +# We're done with this file. + if len(Records) == 0: + break + RecordsRead += 10 + for i in arange(0, 10): + Ptr = RecordSize*i + Record = Records[Ptr:Ptr+RecordSize] +# Need another set of Records. + if len(Record) < RecordSize: + break + ChanID = Record[15:18].decode("latin-1") +# The Q330 may create an "empty" file (all 00H) and then not finish filling it +# in. The read()s keep reading, but there's nothing to process. This detects +# that and returns. This may only happen in .bms-type data. + if ChanID == "\x00\x00\x00": +# I guess if a file is scrambled these could have 1000's of things in them. +# Both of them should only have one thing for baler files. Convert them to +# strings and then chop them off if they are long. + Stas = list2Str(StationIDs) + if len(Stas) > 12: + Stas = Stas[:12]+"..." + Chans = list2Str(Channels) + if len(Chans) > 12: + Chans = Chans[:12]+"..." + return (4, "YB", \ + "%sFILE ENDS EMPTY. Times: %s to %s\n%sRecs: %d(%dB) Stas: %s Chans: %s"% \ + (Prefix, FirstTime, LastTime, Prefix, RecordsRead, \ + RecordSize, Stas, Chans), 2, FirstTime, LastTime) + if ChanID not in Channels: + Channels.append(ChanID) + StaID = Record[8:13].decode("latin-1").strip() + if StaID not in StationIDs: + StationIDs.append(StaID) + Year, Doy, Hour, Mins, Secs, Tttt= struct.unpack(">HHBBBxH", \ + Record[20:30]) + Month, Date = q330yd2md(Year, Doy) + DateTime = "%d-%02d-%02d %02d:%02d"%(Year, Month, Date, Hour, Mins) + if DateTime < FirstTime: + FirstTime = DateTime + if DateTime > LastTime: + LastTime = DateTime + Fp.close() + if RecordsRead == 0: + return (5, "YB", "%sNO DATA."%Prefix, 2, "", "") +# Same as above. + Stas = list2Str(StationIDs) + if len(Stas) > 12: + Stas = Stas[:12]+"..." + Chans = list2Str(Channels) + if len(Chans) > 12: + Chans = Chans[:12]+"..." + if len(StationIDs) > 1: + return (6, "MW", \ + "%sMULTIPLE STATIONS. Times: %s to %s\n%sRecs: %d(%dB) Stas: %s Chans: %s"% \ + (Prefix, FirstTime, LastTime, Prefix, RecordsRead, \ + RecordSize, Stas, Chans), 3, FirstTime, LastTime) + elif len(Channels) > 1: + return (7, "MW", \ + "%sMULTIPLE CHANNELS. Times: %s to %s\n%sRecs: %d(%dB) Stas: %s Chans: %s"% \ + (Prefix, FirstTime, LastTime, Prefix, RecordsRead, \ + RecordSize, Stas, Chans), 3, FirstTime, LastTime) + else: + return (0, "GB", \ + "%sFILE OK. Times: %s to %s\n%sRecs: %d(%dB) Stas: %s Chans: %s"% \ + (Prefix, FirstTime, LastTime, Prefix, RecordsRead, \ + RecordSize, Stas, Chans), 1, FirstTime, LastTime) +# END: checkDTFile + + ############################## # BEGIN: checkForUpdates(What) -# FUNC:checkForUpdates():2019.018 +# FUNC:checkForUpdates():2019.266 def checkForUpdates(What): # Finds the "new"+PROG_NAMELC+".txt" file created by the program webvers at # the URL and checks to see if the version in that file matches the version @@ -649,6 +963,19 @@ def checkForUpdates(What): stdout.write("Checking for updates...\n") elif What == "get": stdout.write("Downloading...\n") +# This security restriction showed up in one place in a different program. +# Don't know if it is a situation that is coming or going. It may prevent an +# ssl CERTIFICATE_VERIFY_FAILED error if things are not right. If this fails +# the urlopen() may too, so just go on, or maybe this system won't even need +# this. + try: + import ssl + from os import environ + if (environ.get("PYTHONHTTPSVERIFY") == None or \ + getattr(ssl, "_create_unverified_context", "") == ""): + ssl._create_default_https_context = ssl._create_unverified_context + except Exception as e: + stdout.write("SSL: %s\n"%e) # Get the file that tells us about the current version on the server. # One line: PROG; version; original size; compressed size try: @@ -662,12 +989,12 @@ def checkForUpdates(What): # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> # How unhandy. if Line.find("DOCTYPE") != -1: - stdout.write("No update information found.\n") + stdout.write("No update information found.\n\n") return 0 - except (IndexError, IOError) as e: - stdout.write("%s\n"%e) + except Exception as e: + stdout.write("URL: %s\n"%e) stdout.write( \ - "There was an error obtaining the version information from PASSCAL.\n") + "There was an error obtaining the version information from PASSCAL.\n\n") return 1 Parts = Line.split(";") for Index in arange(0, len(Parts)): @@ -676,24 +1003,22 @@ def checkForUpdates(What): if What == "check": if PROG_VERSION < Parts[VERS_VERS]: stdout.write( \ - "This is an old version of %s.\nThe current version generally available is %s.\nUse bline.py -UD to get the new version.\n"% \ + "This is an old version of %s.\nThe current version generally available is %s.\nUse bline.py -UD to get the new version.\n\n"% \ (PROG_NAME, Parts[VERS_VERS])) return 0 elif PROG_VERSION == Parts[VERS_VERS]: - stdout.write("This copy of %s is up to date.\n"%PROG_NAME) + stdout.write("This copy of %s is up to date.\n\n"%PROG_NAME) return 0 elif PROG_VERSION > Parts[VERS_VERS]: stdout.write( \ - "Congratulations!\nThis is a newer version of %s than is generally available.\nEveryone else probably still has version %s.\n"% \ + "Congratulations!\nThis is a newer version of %s than is generally available.\nEveryone else probably still has version %s.\n\n"% \ (PROG_NAME, Parts[VERS_VERS])) return 0 elif What == "get": - ZSize = int(Parts[VERS_ZSIZ]) try: GetFile = "new%s.zip"%PROG_NAMELC Fpr = urlopen(VERS_VERSURL+GetFile) - Fpw = open(Dirspec+GetFile, "wb") - DLSize = 0 + Fpw = open(ABSCWDspec+GetFile, "wb") Buffer = Fpr.read() Fpw.write(Buffer) except Exception as e: @@ -706,14 +1031,15 @@ def checkForUpdates(What): Fpw.close() except: pass - stdout.write("Error downloading new version.\n%s"%e) + stdout.write("Error downloading new version.\n%s\n\n"%e) return 1 Fpr.close() Fpw.close() - stdout.write("The current update file has been saved as\n\n %s\n\n"% \ - (AbsDirspec+GetFile)) stdout.write( \ - "Unzip the file, rename new%s.py to %s.py,\nand copy it to its final destination.\n"% \ + "The current update file has been saved as\n\n %s\n\n"% \ + (ABSCWDspec+GetFile)) + stdout.write( \ + "Unzip the file, rename new%s.py to %s.py,\nand copy it to its final destination.\n\n"% \ (PROG_NAMELC, PROG_NAMELC)) return 0 # END: checkForUpdates @@ -721,6 +1047,33 @@ def checkForUpdates(What): +######################################################### +# BEGIN: fileBlockRetrieved(Blocks, BlockSize, TotalSize) +# FUNC:fileBlockRetrieved():2019.234 +# Gets called every 8192 bytes offloaded and urlretrieved() passes the +# argument values. +def fileBlockRetrieved(Blocks, BlockSize, TotalSize): + global OffBytes + global Offloaded10 +# This will be kept updated for any offloading error message. + OffBytes = Blocks*BlockSize +# Will give us 10% 20% 30%... + Offed = int(100.0*OffBytes/TotalSize)//10*10 + if Offed > Offloaded10: + if Offloaded10 == 0: + stdout.write(" ") + Offloaded10 = Offed +# Rounding errors on some systems can make this 110. ??? + if Offloaded10 > 100: + Offloaded10 = 100 + stdout.write(" %d%%"%Offloaded10) + stdout.flush() + return +# END: fileBlockRetrieved + + + + ################### # BEGIN: floatt(In) # LIB:floatt():2018.256 @@ -786,139 +1139,150 @@ def fmti(Value): -######################################################### -# BEGIN: fileBlockRetrieved(Blocks, BlockSize, TotalSize) -# FUNC:fileBlockRetrieved():2017.026 -# Gets called every 8192 bytes offloaded. -def fileBlockRetrieved(Blocks, BlockSize, TotalSize): - global OffFileBytes - global Offloaded10 -# This will be kept updated for any offloading error message. - OffFileBytes = Blocks*BlockSize -# Will give us 10% 20% 30%... - Offed = int(100.0*OffFileBytes/TotalSize)//10*10 - if Offed > Offloaded10: - if Offloaded10 == 0: - stdout.write(" ") - Offloaded10 = Offed -# Rounding errors on some systems can make this 110. ??? - if Offloaded10 > 100: - Offloaded10 = 100 - stdout.write(" %d%%"%Offloaded10) - stdout.flush() - return -# END: fileBlockRetrieved +################################## +# BEGIN: checkTagID(TagID, IPAddr) +# LIB:checkTagID():2019.240 +# Grabs the TagID from the baler.htm page just to check that the user and +# the program are talking about the same baler. +def checkTagID(ITagID, IPAddr): + try: + Ret = readFileLinesRB("http://%s/baler.htm"%IPAddr, True) + if Ret[0] != 0: + return Ret + except IOError as e: + return (2, "MW", "Error getting baler.htm:\n%s"%e, 3) + Lines = Ret[1] + BTagID = "" + for Line in Lines: + Line = Line.upper() + if Line.find("BALER TAGID:") != -1: + Value = intt(Line[(Line.index(">BALER TAGID:")+ \ + len(">BALER TAGID:")):]) + BTagID = str(Value) + break +# Talking to the wrong baler?? + if ITagID != BTagID: + return (1, "RW", "TagIDs do not match: Entered %s, baler %s"% \ + (ITagID, BTagID), 2) + return (0,) +# END: checkTagID ######################################## -# BEGIN: getBalerHtm(Msg, TagID, IPAddr) -# LIB:getBalerHtm():2019.052 -def getBalerHtm(Msg, TagID, IPAddr): - global BState -# Do these simple-minded checks, because you know those users, and it may be -# easier for some programs to let this function check here, instead of figuring -# out what was entered, for example, on the command line. - if TagID.find(".") != -1: - return (2, "RW", "TagID has periods in it: %s"%TagID, 2) - if IPAddr.find(".") == -1: - return (2, "RW", "IPAddr has no periods in it: %s"%IPAddr, 2) - if Msg == "bg": - msgLn(1, "W", "Getting baler.htm from %s..."%TagID) - elif Msg == "bl": - logIt("Getting baler.htm from %s..."%TagID) +# BEGIN: getBalerInfoHtm(ITagID, IPAddr) +# LIB:getBalerInfoHtm():2019.252 +# Gets the index.htm and baler.htm pages and returns basic info about the +# baler. +def getBalerInfoHtm(ITagID, IPAddr): try: - Fp = urlopen("http://%s/baler.htm"%IPAddr) - Ret = readFileLines2(Fp, True) + Ret = readFileLinesRB("http://%s/index.htm"%IPAddr, True) if Ret[0] != 0: - return (1, Ret) - Fp.close() - Lines = Ret[1] + return Ret + except IOError as e: + return (2, "MW", "Error getting index.htm:\n%s"%e, 3) + Lines = Ret[1] + Info = {} + Info["NetSta"] = "?" + for Line in Lines: + Line = Line.upper() +# The line is: <CENTER><H1>Net-Sta INDEX</H1>... + if Line.find("<H1>") != -1: + Parts = Line.split() + Index = Parts[0].index("<H1>") + Info["NetSta"] = Parts[0][Index+4:] + try: + Ret = readFileLinesRB("http://%s/baler.htm"%IPAddr, True) + if Ret[0] != 0: + return Ret except IOError as e: - return (2, "MW", "Error getting baler.htm:\n%s\nStopped."%e, 3) -# Set by hitting the Check button when it is already checking. - if BState == STATE_STOP: - return (1, "YB", "Stopped by user. (%s)"%getGMT(3), 2) - FWVers = "?" - TagID2 = "?" - DSize = 0 - NFiles = 0 - Percent = 0.0 + return (2, "MW", "Error getting baler.htm:\n%s"%e, 3) + Lines = Ret[1] + Info["BModel"] = "?" + Info["FWVers"] = "?" + Info["MACAddr"] = "?" + Info["BTagID"] = "?" + Info["DSize"] = 0 + Info["NFiles"] = 0 + Info["Percent"] = 0.0 + Info["FW2Vers"] = "?" + Info["SerNum"] = "?" + Info["Volts"] = "?" + Info["Temp"] = "?" + Info["DModel"] = "?" +# The baler model and software version are two HTML <li> items, but are on the +# same line, so just check each line for each possibility. All of the other +# items are on a line by themselves. for Line in Lines: Line = Line.upper() + if Line.find(">BALER MODEL: ") != -1: + Info["BModel"] = Line[(Line.index(">BALER MODEL: ")+ \ + len(">BALER MODEL: ")):Line.index("</LI>")] if Line.find(">SOFTWARE VERSION:") != -1: Value = floatt(Line[(Line.index(">SOFTWARE VERSION:")+ \ len(">SOFTWARE VERSION:")):]) - FWVers = str(Value) - continue + Info["FWVers"] = str(Value) + if Line.find(">MAC ADDRESS: ") != -1: + Info["MACAddr"] = Line[(Line.index(">MAC ADDRESS: ")+ \ + len(">MAC ADDRESS: ")):Line.index("</LI>")] if Line.find("BALER TAGID:") != -1: Value = intt(Line[(Line.index(">BALER TAGID:")+ \ len(">BALER TAGID:")):]) - TagID2 = str(Value) - continue + Info["BTagID"] = str(Value) if Line.find("DISK SIZE:") != -1: Value = intt(Line[(Line.index("DISK SIZE:")+ \ len("DISK SIZE:")):]) - DSize = Value - continue + Info["DSize"] = Value if Line.find("PERCENT OF") != -1: Parts = Line.split() - NFiles = intt(Parts[2]) - Percent = floatt(Parts[-1]) - continue + Info["NFiles"] = intt(Parts[2]) + Info["Percent"] = floatt(Parts[-1]) + if Line.find(">DMU2") != -1: + Parts = Line.split() + Info["FW2Vers"] = Parts[2][Parts[2].index("=")+1: \ + Parts[2].index(",")] + Info["SerNum"] = Parts[4][Parts[4].index("=")+1: \ + Parts[4].index("</LI>")] + if Line.find(">SUPPLY VOLTAGE=") != -1: + Info["Volts"] = Line[(Line.index(">SUPPLY VOLTAGE=")+ \ + len(">SUPPLY VOLTAGE=")):Line.index("</LI>")] + if Line.find(">TEMPERATURE=") != -1: + Info["Temp"] = Line[(Line.index(">TEMPERATURE=")+ \ + len(">TEMPERATURE=")):Line.index("</LI>")] + if Line.find(">DISK MODEL: ") != -1: + Info["DModel"] = Line[(Line.index(">DISK MODEL: ")+ \ + len(">DISK MODEL: ")):Line.index("</LI>")] # Talking to the wrong baler?? - if TagID != TagID2: - return (2, "R", \ - "TagIDs do not match: Entered %s, baler %s.\nStopped. (%s)"% \ - (TagID, TagID2, getGMT(3)), 2) - if Msg == "bg": - msgLn(1, "", "Got baler.htm.") - elif Msg == "bl": - logIt("Got baler.htm.") - return (0, FWVers, DSize, NFiles, Percent) -# END: getBalerHtm + if ITagID != Info["BTagID"]: + return (1, "RW", "TagIDs do not match: Entered %s, baler %s"% \ + (ITagID, Info["BTagID"]), 2) + return (0, Info) +# END: getBalerInfoHtm -######################################## -# BEGIN: getFilesHtm(Msg, TagID, IPAddr) -# LIB:getFilesHtm():2019.052 -# Used in many places to decode the List of Lists of files returned by this -# function. -FH_NAME = 0 -FH_SIZE = 1 -FH_FROM = 2 -FH_TO = 3 - -def getFilesHtm(Msg, TagID, IPAddr): - global BState -# Do these simple-minded checks, because you know those users, and it may be -# easier for some programs to let this function check here, instead of figuring -# out what was entered, for example, on the command line. - if TagID.find(".") != -1: - return (2, "RW", "TagID has periods in it: %s"%TagID, 2) - if IPAddr.find(".") == -1: - return (2, "RW", "IPAddr has no periods in it: %s"%IPAddr, 2) - if Msg == "bg": - msgLn(1, "W", "Getting files.htm from %s..."%TagID) - elif Msg == "bl": - logIt("Getting files.htm from %s..."%TagID) +############################################ +# BEGIN: getFilesHtm(MSGspec, TagID, IPAddr) +# LIB:getFilesHtm():2019.235 +# Used in many places to decode the List of Lists of files returned by this +# function. +B_NAME = 0 +B_SIZE = 1 +B_FROM = 2 +B_TO = 3 + +def getFilesHtm(MSGspec, TagID, IPAddr): try: - Fp = urlopen("http://%s/files.htm"%IPAddr) - Ret = readFileLines2(Fp, True) + Ret = readFileLinesRB("http://%s/files.htm"%IPAddr, True) if Ret[0] != 0: - return (1, Ret) - Fp.close() + return Ret Lines = Ret[1] except IOError as e: - return (1, "MW", "Error getting files.htm:\n%s\nStopped. (%s)"%(e, \ - getGMT(3)), 3) - if BState == STATE_STOP: - return (1, "YB", "Stopped by user. (%s)"%getGMT(3), 2) - FHFiles = [] - FHBytes = 0 + return (1, "MW", "Error getting files.htm:\n%s"%e, 3) + BFiles = [] + BBytes = 0 for Line in Lines: # The line case is mixed, but the file names are always all uppercase, so just # do that. @@ -941,19 +1305,12 @@ def getFilesHtm(Msg, TagID, IPAddr): Size = int(Parts[2]) From = "%s %s"%(Parts[5], Parts[6]) To = "%s %s"%(Parts[8], Parts[9]) - FHFiles.append([Filename, Size, From, To]) - FHBytes += Size - if len(FHFiles) == 0: - return (1, "Y", "No files found in files.htm.\nStopped. (%s)"% \ - getGMT(3)) - if BState == STATE_STOP: - return (1, "YB", "Stopped by user. (%s)"%getGMT(3), 2) - FHFiles = listSort(FHFiles, FH_NAME, "a") - if Msg == "bg": - msgLn(1, "", "Got files.htm.") - elif Msg == "bl": - logIt("Got files.htm.") - return (0, FHFiles, FHBytes) + BFiles.append([Filename, Size, From, To]) + BBytes += Size + if len(BFiles) == 0: + return (1, "Y", "No files found in files.htm.", 2) + BFiles = listSort(BFiles, B_NAME, "a") + return (0, BFiles, BBytes) # END: getFilesHtm @@ -1048,6 +1405,738 @@ def getGMT(Format): +###################################################################### +# BEGIN: getSetSettings(SETspec, Cmd, Ieth = "", Iip = "", Iport = "") +# FUNC:getSetSettings():2019.289 +# The settings file contains the Ethernet port, IP address and port number +# for a baler. The file is usually <tagid>.set and the format is: +# Ethernet device; IP address; port number +# The file will be created/updated when the -b command finds a baler, or +# when the user supplies the IP address to the -b command. +# With the '-b <ipaddr>' command the Ethernet device and port number will +# be blank. +def getSetSettings(SETspec, Cmd, Ieth = "", Iip = "", Iport = ""): + if Cmd == "get": + try: + if exists(SETspec) == False: + return (1, "RW", "There is no settings file %s"% \ + basename(SETspec)) + Ret = readFileLinesRB(SETspec, True) + if Ret[0] != 0: + return (1, "MW", "Error reading settings file.") + Lines = Ret[1] + Parts = Lines[0].split(";") + Parts += [""]*(3-len(Parts)) + for Index in arange(0, len(Parts)): + Parts[Index] = Parts[Index].strip() + except Exception as e: + return (1, "MW", "Error reading settings file.\n%s"%e) +# This one has to be here and look right, so might as well check for the +# caller. + if Parts[1] == "": + return (1, "No IP address found in file %s.set."%CLTagID) + if isIPV4Addr(Parts[1]) == False: + return (1, \ + "The %s file IP address '%s' does not look right."% \ + (basename(SETspec), Parts[1])) + return (0, Parts[0], Parts[1], Parts[2]) + elif Cmd == "set": + try: + Fp = open(SETspec, "w") + Fp.write("%s;%s;%s\n"%(Ieth, Iip, Iport)) + Fp.close() + except: + return (1, "MW", "Error writing settings file.") + return (0,) +# END: getSetSettings + + + + +################### +# BEGIN: helpLong() +# FUNC:helpLong():2019.297 +def helpLong(): + HELPText = "USING BLINE.PY\n\ +--------------\n\ +BLINE is a Python command line program that offloads data files from a\n\ +Quanterra PB14F Baler on Linux, macOS and Windows. It may also be used to\n\ +set up the baler prior to offloading by assigning the baler an IP address,\n\ +and erase data and clear the Q330 association before returning the baler\n\ +to the field.\n\ +\n\ +The baler and the control computer must be on the same Ethernet sub-net\n\ +for them to communicate. Every time the baler is powered up it defaults\n\ +to an IP address and broadcasts its presence. BLINE or Quantera's BaleAddr\n\ +program must be used to set the baler's address to one compatible with\n\ +the address of the control computer.\n\ +\n\ +If using BaleAddr, and the computer IP address is something like\n\ +129.138.26.1, the baler's address should be set to something like\n\ +129.138.26.2 for the two to communicate. The desired IP address for the\n\ +baler is entered in BaleAddr's 'IP Address to Assign' field and the ATTN\n\ +button on the baler is then pressed. When the baler finishes booting\n\ +BaleAddr handshakes with the baler and sets the IP address to the entered\n\ +value.\n\ +\n\ +When using BLINE to set the IP address the baler should be connected the\n\ +same way as for BaleAddr, and then the 'bline.py <tagid> -b' command\n\ +issued. When directed by BLINE the ATTN button on the baler should be\n\ +pressed. BLINE will detect the IP address of the control computer and\n\ +select an IP address for the baler once the baler is finished booting.\n\ +Some caveats to this process are listed below.\n\ +\n\ +\n\ +IP ADDRESS CAVEATS WITH BLINE (AND BALEADDR)\n\ +--------------------------------------------\n\ +Not all versions of operating systems or versions of Python support all\n\ +of the necessary networking functions for the new BLINE to 'automatically'\n\ +determine where a baler 'is coming from' when it initially broadcasts\n\ +its 'I am here' message. In those cases BLINE must be told the name of\n\ +the Ethernet device where the baler will show up. This is related to\n\ +having to disable the WiFi system on a laptop before using BaleAddr to\n\ +set the IP address. BaleAddr can also have trouble figuring out to which\n\ +device the baler is connected. See the options for the -b comand.\n\ +\n\ +Depending on the operating system of the control computer, and sometimes\n\ +the age of the computer, the IP address of the Ethernet port may need to be\n\ +manually set using the operating system preferences utilities, and that\n\ +may not even be good enough. Some operating systems and/or computers do\n\ +not set the Ethernet port IP address until they are connected to a network.\n\ +If the baler is connected directly to the Ethernet port there is no\n\ +network until the baler wakes up with an ATTN button press. For BaleAddr\n\ +use it is then too late. You need to know the address to place into the\n\ +address field before waking up the baler.\n\ +\n\ +Some systems will set the Ethernet device IP address to a \"self-assigned\"\n\ +address once the ATTN button has been pressed. Again this is too late for\n\ +BaleAddr, but for BLINE it may work if the address is set quickly enough\n\ +while the baler is booting up.\n\ +\n\ +If the device's IP address cannot be set, or does not get set itself in\n\ +time to begin talking to a baler by the time it is ready the problem can\n\ +usually be solved by placing a hub or switch in the Ethernet line between\n\ +the control computer and the baler. Something like a Netgear GS105 may\n\ +be used, or a PASSCAL Baler Offload Box. Connecting the computer's Ethernet\n\ +port to a switch/hub should keep the port alive so it will keep a manually\n\ +assigned or be assigned a self-assigned IP address before the baler ATTN\n\ +button is pressed.\n\ +\n\ +Windows 7 and 10 control computers that were connected to the Internet were\n\ +assigned an IP address by DHCP. BLINE was started with the -b command and\n\ +the network cable was removed and one directly connected to the baler was\n\ +connected. When the baler was started BLINE assigned an IP address to the\n\ +baler that would work with the computer's DHCP address. After a couple of\n\ +minutes communication with the baler failed. The operating system or the\n\ +Ethernet hardware detected that the port was not connected to the actual\n\ +Internet (just another device). Windows decided to drop the DHCP IP\n\ +address and create a self-assigned address for the device. At that point\n\ +BLINE could not talk to the baler. Shutting down the baler and reissuing\n\ +the -b command got the computer and baler back on the same sub-net. This\n\ +may also happen with macOS.\n\ +\n\ +On Linux some files may need to be modified to allow the operating system\n\ +to detect the initial broadcast message from the baler. If modifying the\n\ +file(s) is required the password for the user account (the 'sudo password')\n\ +will be requested to allow BLINE to make those changes. The request may\n\ +be made more than once, but following that the files should not need to\n\ +be changed again until the control computer has been shutdown or rebooted.\n\ +\n\ +macOS is SO safe that it wants to approve the connection of a baler to\n\ +the Python interpreter at least once during a session when using the -b\n\ +command. When this happens you will need to click the Allow button and\n\ +the -b command will continue, but will probably fail to set the baler's IP\n\ +address. If you are quick you can reissue the -b command (use the Up-Arrow\n\ +key if using Terminal), and if the baler has not shut down (LEDs are still\n\ +green) the baler will try to connect again in about 1 minute. You do not\n\ +need to press the ATTN button to shut the baler down and then start it\n\ +back up. If you do not reissue the -b command in time the baler will shut\n\ +itself down after about 1.5 minutes.\n\ +\n\ +The reissuing trick for the -b command above after a failure, but before\n\ +the baler shuts down, may be used on all operating systems. The baler\n\ +always tries to connect again before it gives up.\n\ +\n\ +\n\ +GENERAL OPERATIONS\n\ +==================\n\ +INSTALLING AND RUNNING BLINE\n\ +----------------------------\n\ +BLINE is a single Python program file, so you need Python 2 or 3 installed\n\ +to run it. The advanced commands (-b -c -n -s -x) require packages and\n\ +modules that may not normally be installed in a standard Python\n\ +installation. These can usually be installed using 'pip' or 'pip3' (\"Pip\n\ +Installs Packages\"). Using any of the above commands will cause BLINE to\n\ +check to see if any of the modules need to be installed, so test before\n\ +going to the field where there may be no Internet.\n\ +\n\ +Using BaleAddr to set the baler IP address and using the \"old\" BLINE\n\ +commands for offloading files does not require extra Python modules.\n\ +\n\ +To install BLINE copy the file bline.py to where ever you want. If it is\n\ +placed somewhere like /opt/passcal/bin/ (the preferred choice if the\n\ +PASSCAL software package has been installed), with the other PASSCAL\n\ +software, then it should be in the path set up by the PASSCAL software\n\ +installation, and just \"bline.py\" on the command line of an xterm or\n\ +Terminal window will start execution. For that location this command will\n\ +probably have to be used\n\ +\n\ + sudo cp bline.py /opt/passcal/bin\n\ +\n\ +and the login password given to copy it there. No matter where it is\n\ +installed it will also probably have to made executable with the command\n\ +\n\ + chmod +x bline.py\n\ +\n\ +Depending on where it is copied \"sudo\" may also need to be added before\n\ +chmod. If it is not executable a 'permission denied' message will be\n\ +displayed when trying to execute the program.\n\ +\n\ +Installing it to any other location will require ensuring that the location\n\ +is in the current path and the necessary entries have been made in files\n\ +such as .bashrc, or .bash_profile, or any other initialization files for\n\ +the command shell in use, otherwise you will need to preface 'bline.py'\n\ +with the required path to execute it.\n\ +\n\ +After installation you can check if any additional modules need to be\n\ +installed by running the command\n\ +\n\ + bline.py 5555 -b\n\ +\n\ +On Windows you may need \"python\" in front of the bline.py. On macOS and\n\ +Linux you probably won't. The 5555 does not have to be a real baler TagID\n\ +to run this test. The modules needing to be installed will be listed.\n\ +Remember, this is only if you are going to use BLINE to assign the baler's\n\ +IP address or clean the baler for redeployment, instead of BaleAddr.\n\ +\n\ +PIP\n\ +If you enter 'pip' on the command line and it does not exists it can be\n\ +installed using\n\ +\n\ + python -m ensurepip --default-pip\n\ +\n\ +If, or once pip is installed the usual command for installing modules is\n\ +\n\ + pip install <module name>\n\ +\n\ +Known extra modules that may be needed. These may not all be required on\n\ +all systems or for every command, but to use any of the commands you will\n\ +need to install all of them:\n\ + -b -c -n -s -x commands:\n\ + psutil\n\ + subprocess32 (for Python2)\n\ + ipaddress\n\ + pexpect (for Linux)\n\ +\n\ +Using the 'ensurepip' install method may install a very old version of\n\ +pip. If pip says there is a new version you can install it using the\n\ +update instructions that will be displayed.\n\ +\n\ +\n\ +STARTING BLINE\n\ +--------------\n\ +BLINE is a command line program and to run it you start a Terminal/xterm\n\ +window in the directory where you want bookkeeping files and the offloaded\n\ +data files for a baler to be saved. The command to start the program\n\ +depends on how BLINE was installed and the operating system as described\n\ +above. Using no command line arguments will list all of the commands (the\n\ +Short Help -- you are reading the Long Help) for the program. Any of these\n\ +may be the correct way to start BLINE:\n\ +\n\ +\n\ + bline.py\n\ +or\n\ + <path to bline>/bline.py\n\ +or\n\ + ./bline.py\n\ +or\n\ + python bline.py\n\ +\n\ +\n\ +SETTING THE BALER IP ADDRESS\n\ +----------------------------\n\ +Using BaleAddr\n\ +--------------\n\ +Once the baler has been connected to the control computer and the Ethernet\n\ +IP address of the control computer has been determined (see the section\n\ +on IP address caveats above) enter the address you want the baler to be\n\ +set to into the 'IP Address to Assign' field in the program's display and\n\ +press the ATTN button. Once the baler boots up it should be assigned the\n\ +address that was entered if the address is reachable by the control\n\ +computer.\n\ +\n\ +This version of BLINE differs from the original version in that you do not\n\ +provide the baler's address on the command line for each command, however,\n\ +when using BaleAddr to set the IP address BLINE must still be told what the\n\ +baler's address was set to. This is done using the -b command\n\ +\n\ + bline.py <tagid> -b <ipaddr>\n\ +\n\ +The entered <ipaddr> will be written to a file named <tagid>.set and\n\ +BLINE will read that file each time a command needs the address.\n\ +\n\ +Using BLINE\n\ +-----------\n\ +Provided the cavets in the section above are understood setting the IP\n\ +address of the baler using BLINE after the baler has been connected is\n\ +done using\n\ +\n\ + bline.py <tagid> -b\n\ +\n\ +BLINE will start and wait for the ATTN button of the target baler to be\n\ +pushed and that baler to boot up. When/if the baler is seen by BLINE its\n\ +address will be set, then the time in the baler, and then the baler will\n\ +be \"pinged\" to see that it can be communicated with.\n\ +\n\ +If BLINE is being used on a system that does not fully support the\n\ +networking functions for automatically determining the correct Ethernet\n\ +device the baler is trying to connect through, BLINE can be told which\n\ +device to watch using\n\ +\n\ + bline.py <tagid> -b <ethdev>\n\ +\n\ +It will depend on the operating system, but this will be something like\n\ +en0 or enp1s0. On Windows systems it may be something like \"Local\n\ +Area Connection\". In this case it is recommended that the network\n\ +adapter for the Ethernet device in Control Panel | Network Connections\n\ +be renamed to something sensible, like \"en0\".\n\ +\n\ +\n\ +When everything works for setting the IP address of the baler with\n\ +BaleAddr or BLINE, you should get the message\n\ +\n\ + Baler OK\n\ +\n\ +When using either program if the 'Baler OK' message doesn't show up then\n\ +the program may not really be talking to the baler and/or the baler's IP\n\ +address did not get set. It happens a lot. Sometimes commands after this\n\ +when using BLINE may work, but most often not. Trying again after checking\n\ +the wiring and any entered IP address or Ethernet device name is the best\n\ +ideas.\n\ +\n\ +Quiting/Resetting BaleAddr, re-pressing the ATTN button on the baler to\n\ +let the baler shut down, restarting BaleAddr (if it was Quit), and pressing\n\ +the ATTN button again up to five times has been sucsessful at geting\n\ +BaleAddr and the baler to connect. It's just flakey. Make sure the Ethernet\n\ +port has or is getting set to an address as described in the caveats.\n\ +If you get up to maybe three cycles you can try shutting down the baler\n\ +and then disconnecting the power and reconnecting. Sometimes that helps.\n\ +Make sure you have a suitable IP address in BaleAddr that is compatible\n\ +with the address the control computer thinks it has. That REALLY helps.\n\ +Also make sure the WiFi network is turned off like you have to do with\n\ +EzBaler. None of the programs can figure out which Ethernet adapter to use\n\ +when there is more than one active, except BLINE when using the -b command.\n\ +In testing BLINE seems to be better.\n\ +\n\ +All of the past and current conection problems may be because of the IP\n\ +address funny business described in the caveats section above.\n\ +\n\ +\n\ +WHERE'S MY STUFF?\n\ +-----------------\n\ +All files and directories created by BLINE pertaining to a particular\n\ +baler are created in the current working directory where BLINE is started,\n\ +and they all start with the <tagid> of the baler. For example 5520.msg\n\ +contains most of the text messages displayed while using BLINE and is a\n\ +running log record of what was performed for baler 5520. Some simple\n\ +status messages and errors are not saved. Data files offloaded from the\n\ +baler will be in the directory named 5520.sdr. .sdr is a reference to\n\ +the Q330 program sdrsplit, that is used to split multiplexed data files\n\ +into files containing only one Q330 channel per file, which is how\n\ +information is recorded on the balers. The .sdr also triggers the program\n\ +QPEEK to look for one-channel-per-file data in the directory. 5520.set\n\ +is new for this BLINE version and it contains the IP information for\n\ +the baler. 5520files.txt is a listing of the data files on the baler that\n\ +is created/rewritten each time a command is executed that asks the baler\n\ +for the list of files.\n\ +\n\ +\n\ +STOPPING BLINE\n\ +--------------\n\ +As usual, Ctrl-C should stop any BLINE operation.\n\ +\n\ +\n\ +COMMAND DETAILS\n\ +===============\n\ +BLINE is designed to be run in or started from the directory where you want\n\ +to work. You cannot use paths to directories. The current working directory\n\ +or the \"work directory\" must be set by changing to the directory of\n\ +choice using the command line with something like the 'cd' command before\n\ +starting the program. Files created by the program will be written the the\n\ +work directory and offloaded files will be written to a sub-directory of\n\ +the work directory (./<tagid>.sdr). Verification commands, -v/-vl and -V,\n\ +expect the program to be started in the same directory where it was\n\ +running when the files were offloaded from the baler -- if they have not\n\ +been moved since offloading them.\n\ +\n\ +All of the commands are entered on the command line. There are several:\n\ +versions of the command line:\n\ +\n\ + bline.py <command>\n\ + bline.py <tagid> <command>\n\ + bline.py <tagid> <command> [<files>]\n\ + bline.py <tagid> <command> <files>\n\ + bline.py <tagid> -M <message>\n\ + bline.py <tagid> -b [<ipaddr> | <ethdev>]\n\ +\n\ +See the -h help for the command line items that are required or optional\n\ +for each command.\n\ +\n\ +The <tagid> is used as part of the bookkeeping file names and the directory\n\ +name where the offloaded data files will be placed. It is also used by\n\ +BLINE to make sure it is talking to the baler the user thinks it is\n\ +talking to.\n\ +\n\ +Some commands allow, and some require, a space-separated list of file\n\ +names with or without standard UNIX wildcard characters to follow them\n\ +to control which data files are delt with. This may, for example, be a\n\ +list of files to offload, a list of files to not offload, or it may be a\n\ +list of files to examine. It depends on the command. Depending on the\n\ +operating system the the way the command shell is set up you may need to\n\ +enclose file names on the command line in double quotes when using\n\ +wildcard characters For example \"*\", instead of just *, or \"*.BZN\",\n\ +instead of *.BZN. In most cases not supplying any file list is the same\n\ +as using \"*\", i.e. 'all files'. Again it depends on the command.\n\ +\n\ +BaleAddr and/or one of the -b command variations of BLINE must be used to\n\ +set the IP address of the baler before any of the baler-related commands\n\ +will function.\n\ +\n\ +\n\ +LIST OF COMMANDS\n\ +----------------\n\ +These are grouped according to the similarity of the command line arguments.\n\ +\n\ +bline.py <command>\n\ +------------------\n\ +-c\n\ +This command asks the computer running BLINE to print its hostname and\n\ +IP address. This address may be used to help select an IP address to set\n\ +the baler to in the BaleAddr program. The address printed may or may not\n\ +be correct or useful. Some operating systems will not print the correct\n\ +address until the computer is already talking to a baler. Some operating\n\ +systems will report the wrong address if both the wired and wireless\n\ +Ethernet systems are in use. The list goes on. An address of 127.0.0.1\n\ +or 127.0.1.1 will be displayed if the 'default' port's address is not set.\n\ +The default port used by this version of the -c command may not even be\n\ +the correct port.\n\ +\n\ +The most reliable way to determine the IP address of the computer for\n\ +running BaleAddr is to look in the system preferences or Ethernet adapter\n\ +information for the operating system in use. See the second -c command\n\ +below.\n\ +\n\ +-h and -H\n\ +The -h command prints a summary list of possible command line arguments\n\ +to the display. The -H command prints a more detailed help document to the\n\ +display.\n\ +\n\ +-n\n\ +Show the network device/interface names on the control computer. The list\n\ +can be quite long, and it may be useless in Windows, but it may help\n\ +when the Ethernet device name must be supplied for the -b command.\n\ +\n\ +-U, -UD\n\ +If the control computer is connected to the Internet running\n\ +\n\ + bline.py -U\n\ +\n\ +will check to see if there is a newer version of the program than the one\n\ +running. If the response indicates that there is a newer version use the\n\ +-UD command to obtain it. Unzip the file that is downloaded and install\n\ +the new version.\n\ +\n\ +\n\ +bline.py <tagid> <command>\n\ +--------------------------\n\ +-A\n\ +Combines all of the offloaded data file into the file <tagid>.ALL. This\n\ +command may try to create a file larger than the operating system can\n\ +handle, so be careful.\n\ +\n\ +-b\n\ +This command, without any options after the -b, obtains the IP address of\n\ +the control computer and sets the IP address of a baler to a compatible\n\ +address after its ATTN button has been pressed and it finishes booting.\n\ +\n\ +Assigning the IP address to the baler this way records the Ethernet device\n\ +name, IP address used, and the port number to the file <tagid>.set. The\n\ +device name and port number are required for the version of the -c command\n\ +described below, as well as the -s and -x commands. All other commands\n\ +only need the IP address from the .set file (see the '-b <ipaddr>' command\n\ +description below).\n\ +\n\ +-c\n\ +This is a second version of the -c command which uses the actual Ethernet\n\ +device on the control computer that the baler was detected on and returns\n\ +the IP address of that device. The -b command must have been used to set\n\ +the address of the baler for this command to function.\n\ +\n\ +-G, -GD\n\ +These read the offloaded baler files and concatenate all of the files for\n\ +a channel into one channel file. Depending on the operating system this\n\ +may try to create a file larger than the OS can handle, but those days\n\ +are mostly gone. The original files are left in the baler's .sdr directory.\n\ +\n\ +The -G version creates the new files in the baler's .sdr directory. The\n\ +-GD version creates the new files in the directory \"./DATA/\".\n\ +\n\ +Providing a baler TagID will concatenate the files for that one baler.\n\ +Using a baler TagID of 9999 will tell BLINE to look for all of the\n\ +<baler>.sdr directories and perform the concatenation on the data files\n\ +in each baler's directory.\n\ +\n\ +-i\n\ +This command simply establishes a connection with the spcified baler and\n\ +displays the basic version and usage information from the baler.\n\ +\n\ +-l (ell)\n\ +This command requests the list of data files the baler thinks are on its\n\ +internal disk, and displays the list and saves the list to the file\n\ +\n\ + <tagid>files.txt\n\ +\n\ +This list is also saved every time BLINE is commanded to offload data\n\ +files from a baler.\n\ +\n\ +-L\n\ +This lists the files that are in the <tagid>.sdr directory that have\n\ +presumably been offloaded from the baler.\n\ +\n\ +-s\n\ +This command shuts down the baler. It's usually easier to just use the ATTN\n\ +button. The -b command must have been used to set the baler's IP address\n\ +for this command to work.\n\ +\n\ +-x\n\ +This command cleans the baler and clears the Q330 association. You will\n\ +get one chance to cancel the operation before it starts. While the baler\n\ +is busy \"Waiting...\" will be displayed every 20 seconds. The -b command\n\ +must have been used to set the baler's IP address for this command to work.\n\ +Be careful.\n\ +\n\ +\n\ +bline.py <tagid> <command> [<files>]\n\ +------------------------------------\n\ +-O (big oh)\n\ +This command retrieves the list of files on the baler and then goes through\n\ +the list and offloads all of them. The files are placed in the directory\n\ +<tagid>.sdr. A list of space-separated files may be entered to only offload\n\ +specific files or groups of files using standard UNIX wildcards.\n\ +\n\ +Before offloading the <tagid>.sdr directory is examined and files that\n\ +appear to already be fully offloaded (not partially offloaded) will be\n\ +removed from the download list. A warning stating that some files may be\n\ +overwritten will be shown if partially offloaded files are found. This\n\ +applies to all of the file offloading commands.\n\ +\n\ +-o (small o)\n\ +This command retrieves the list of files on the baler and then offloads\n\ +the files whose channel names, like .HHZ, do not start with the letters\n\ +H or S (as specified in the SEED manual as being 'high speed' channel\n\ +names). The files are placed in the directory <tagid>.sdr. A list of\n\ +space-separated files may be entered to only offload specific files or\n\ +groups of files using standard UNIX wildcards.\n\ +\n\ +-v (little vee) and -vl (vee ell)\n\ +The -v/-vl commands get the list of files from the baler and then look\n\ +to see if all of the files on that list are in the <tagid>.sdr directory\n\ +on the computer and if they are the same size as they are in the list. The\n\ +function does not do any checksum checking, because there's no way to get\n\ +the baler to compute a checksum value of the files on the baler to verify\n\ +against. A <files> list may be supplied if only specified files are to\n\ +be verified. The -vl command additionally lists the files that have not\n\ +been offloaded from the baler.\n\ +\n\ +Both commands then go through the data files on the control computer and\n\ +list files in the <tagid>.sdr directory that are not on the list obtained\n\ +from the baler and which should not be in the .sdr directory.\n\ +\n\ +-V (big V)\n\ +The -V command reads block-by-block through the offloaded data file(s)\n\ +in the <tagid>.sdr directory for the specified baler and collects and\n\ +reports:\n\ +\n\ + 1. The earliest block header time in a file\n\ + 2. The latest block header time in a file\n\ + 3. The number of blocks read, and the block size in a file\n\ + 4. A list of all of the station IDs in a file (should normally only\n\ + be one)\n\ + 5. A list of all of the channel names in a file (should normally only\n\ + be one)\n\ +\n\ +For baler data files the normal block size is 4096 bytes and there are\n\ +normally 4100 blocks per 16MB data file.\n\ +\n\ +Things to look for after the function runs are bad/scrambled/missing\n\ +dates and times, multiple station names, multiple channel names and the\n\ +program crashing. The latest block time will not normally be the same\n\ +as the last sample time for each channel, because this is only a block-\n\ +level reading. For quiet and low sample rate channels the time may be\n\ +quite a bit earlier than the last sample time.\n\ +\n\ +This function does not indicate partially offloaded files. It only checks\n\ +the integrity of the files.\n\ +\n\ +A summary of information is printed after all of the files have been\n\ +examined.\n\ +\n\ +\n\ +bline.py <tagid> <command> <files>\n\ +----------------------------------\n\ +-e\n\ +The -e command is basically the same as -e, except that it allows specified\n\ +files (in the <files> list) to be excluded from offloading. If a baler\n\ +offload session always fails on a specific file then the -e command and\n\ +that file's name could be specified, so that it will be skipped and the\n\ +rest of the files will be offloaded. The command only offloads the low\n\ +sample rate files.\n\ +\n\ +-E\n\ +Same as -e, but it will try to offload all files that are not in the list\n\ +of files to exclude.\n\ +\n\ +-F\n\ +Only the file(s) specified in the <files> argument will be offloaded.\n\ +This could be used for a different form of only offloading the low sample\n\ +rate data files. A command like\n\ +\n\ + bline.py 6003 -F \"DT0001*\"\n\ +\n\ +would offload the first file of each channel, which would also include a\n\ +little bit of the high sample rate data, but only the first 16MB file.\n\ +\n\ +\n\ +bline.py <tagid> -M <message>\n\ +-----------------------------\n\ +-M\n\ +Follow the -M with a message that will be displayed and also written to\n\ +the .msg file for the baler. The text may need to be enclosed in quotes:\n\ +\n\ + bline.py -M \"Baler 5549 is station NUUK\"\n\ +\n\ +on some systems.\n\ +\n\ +\n\ +bline.py <tagid> -b [<ipaddr> | <ethdev>]\n\ +-----------------------------------------\n\ +This is the second form of the -b command that must be used if the baler\n\ +IP address is set using BaleAddr by supplying the <ipaddr> set by BaleAddr.\n\ +This will write the supplied IP address to the file <tagid>.set. This must\n\ +be done before any of the baler-related commands will function.\n\ +\n\ +This form of the -b command may also be used to supply the name of the\n\ +Ethernet device that BLINE should watch when setting the IP address. This\n\ +will be something like en0 or enp1s0. Systems that fully support the\n\ +networking functions BLINE uses will determine this value automatically.\n\ +\n\ +\n\ +END\n" + logIt("", "", False) + logIt("", HELPText, False) + return +# END: helpLong + + + + +#################### +# BEGIN: helpShort() +# FUNC:helpShort():2019.297 +def helpShort(): + HELPshort = "bline.py <command>\n\ + -h = This help.\n\ + -H = More help.\n\ + -c = Reports what may be the control computer's IP address and\n\ + other information.\n\ + -n = Lists the Ethernet devices on the control computer. May help\n\ + with the -b command if the device name must be supplied.\n\ + -U = Checks for a newer program version at PASSCAL if connected\n\ + to the Internet (the program will eventually be distributed\n\ + using git and this will go away.\n\ + -UD = Downloads most recent version from PASSCAL (try -U first).\n\ +\n\ +bline.py <tagid> <command>\n\ + -A = Copies all of the offloaded files into <tagid>.ALL\n\ + -c = This is a second version of -c which uses the actual Ethernet\n\ + device that the -b command found for the <tagid> baler.\n\ + -G = Copies all of the offloaded files for each channel into a single\n\ + file in the baler's .sdr directory.\n\ + -GD = Copies all of the offloaded files for each channel into a single\n\ + file in the ./DATA directory.\n\ + -i = Gets basic information from the baler.\n\ + -l = (ell) Displays and saves the list of files on the baler.\n\ + -L = Displays the list of files in the baler's .sdr directory.\n\ + -s = Shuts down the baler.\n\ + -x = Cleans the baler and clears any Q330 association.\n\ +\n\ +bline.py <tagid> <command> [<files>]\n\ + -O = (big oh) Offloads all data files that have not been offloaded.\n\ + -o = (little oh) Offloads low sample rate data files that have not been\n\ + offloaded.\n\ + -v = Gets the baler's list of files and checks the offloaded files\n\ + to see how many are missing or have only been partially\n\ + offloaded.\n\ + -vl = (vee ell) Same as -v, except this also lists the files that have\n\ + not been offloaded.\n\ + -V = Examines offloaded files for bad blocks.\n\ +\n\ +bline.py <tagid> <command> <files>\n\ + -e = Excludes the specified file(s) during an offload, otherwise\n\ + low sample rate files will be offloaded (like -o).\n\ + -E = Excludes the specified file(s) during an offload, otherwise\n\ + all files will be offloaded (like -O).\n\ + -F = Offloads only the specified file(s).\n\ +\n\ +bline.py <tagid> <command> <message>\n\ + -M = Follow the -M with a text message.\n\ +\n\ +bline.py <tagid> <command> [<ipaddr> | <ethdev>]\n\ + -b = Watch for the specified baler and assign it an IP address. To\n\ + make BLINE assign the address leave off the <ipaddr>. If BaleAddr\n\ + is used enter the <ipaddr> that BaleAddr set the baler to\n\ + before running other commands. Supply the <ethdev> if the\n\ + operating system cannot support automatically looking through\n\ + all Ethernet devices and assigning the IP address to the baler.\n\ +\n\ +<tagid> = The tag ID on the front of the baler.\n\ +<ipaddr> = The IP address assigned to the baler by BLINE or BaleAddr.\n\ +<files> = A list of space-separated file names (can have wildcards).\n\ +\n\ +-E/-e commands are, for example, for excluding \"problem\" files that\n\ + may be stopping the offload process.\n\ +*, ?, [] UNIX file wildcards may be used in <files>.\n\ +On some systems you may need to enclose file names using wildcards with\n\ + quotes like \"*.VER\" \"DT0001*\"\n\ +Only one command may be executed per run.\n\ +Always leave a space after the command line switches:\n\ + -E file, not -Efile\n" + logIt("", HELPshort, False) + return +# END: helpShort + + + + +##################### +# BEGIN: helpVShort() +# FUNC:helpVShort():2019.297 +def helpVShort(): + logIt("", "%s %s\nPython %s"%(PROG_NAME, PROG_VERSION, PROG_PYVERSION), \ + False) + HELPVshort = "bline.py [ -h | -H | -c | -n | -U | -UD ]\n\ +bline.py <tagid> [ -A | -c | -G | -GD | -i | -l | -L | -s | -x ]\n\ +bline.py <tagid> [ -o | -O | -v | -vl | -V ] [<files>]\n\ +bline.py <tagid> [ -e | -E | -F ] <files>\n\ +bline.py <tagid> -M <message>\n\ +bline.py <tagid> -b [<ipaddr> | <ethdev>]\n" + logIt("", HELPVshort, False) + return +# END: helpVShort + + + + ################# # BEGIN: intt(In) # LIB:intt():2018.257 @@ -1079,6 +2168,45 @@ def intt(In): +##################### +# BEGIN: isHigh(File) +# FUNC:isHigh(File):2019.288 +# Returns True if the channel name says the file is a high sample rate file. +# This is according to the PASSCAL channel naming rules. +def isHigh(File): + if File.find("_.F") == -1 and File.find("_.G") == -1 and \ + File.find("_.C") == -1 and File.find("_.D") == -1 and \ + File.find("_.H") == -1 and File.find("_.E") == -1 and \ + File.find("_.B") == -1 and File.find("_.S") == -1: + return False + return True +# END: isHigh + + + + +########################### +# BEGIN: isIPV4Addr(IPAddr) +# FUNC:isIPAddr(IPV4Addr):2019.224 +# Looks over the passed IPAddr to see if it might be an IPV4 address. +# A : in the first section and last is also allowed for dev:1.1.1.1:port. +def isIPV4Addr(IPAddr): + Parts = IPAddr.split(".") + if len(Parts) != 4: + return False + for P in [0, 3]: + if Parts[P].find(":") != -1: + N = Parts[P].split(":")[1] + Parts[P] = N + for P in arange(0, 3): + if len(Parts[P].strip()) == 0: + return False + return True +# END: isIPV4Addr + + + + ######################################################################## # BEGIN: list2Str(TheList, Delim = ", ", Sort = True, DelBlanks = False) # LIB:list2Str():2018.235 @@ -1136,13 +2264,19 @@ def listSort(InList, Index, How, Direction = ""): -################### -# BEGIN: logIt(Msg) -# FUNC:logIt():2019.018 -def logIt(Msg): -# Some messages will come with multiple lines (like from LIB functions that -# also need to work with bgoff.py). Split those up. Some messages will come -# with (date time) at the end (again, for bgoff.py). Strip those off. +######################################### +# BEGIN: logIt(MSGspec, Msg, Time = True) +# FUNC:logIt():2019.252 +# Set MSGspec to "" to just get message to stdout. +# Time = Control timestamp at start of message. +def logIt(MSGspec, Msg, Time = 1): + if MSGspec != "": + if exists(MSGspec) == False: + Fp = open(MSGspec, "w") + Fp.close() +# Some messages will come with multiple lines (like from LIB functions). Split +# those up. Some messages may come with (date time) at the end. Strip those +# off. Lines = Msg.split("\n") for Index in arange(0, len(Lines)): # If we happen to go over UT midnight this won't be caught. I'll risk it. @@ -1151,87 +2285,174 @@ def logIt(Msg): Lines[Index] = Lines[Index][0:I] except: continue +# Do the stdout stuff and then the file stuff. for Line in Lines: if Line != "": - stdout.write("%s %s\n"%(getGMT(3), Line)) + if Time == False: + stdout.write("%s\n"%Line) + else: + stdout.write("%s %s\n"%(getGMT(3), Line)) else: stdout.write("\n") stdout.flush() - Fp = open(Msgspec, "a") - for Line in Lines: - if Line != "": - Fp.write("%s %s\n"%(getGMT(3), Line)) - else: - Fp.write("\n") - Fp.close() + if MSGspec != "": +# Don't write a bunch of trailing blank lines to the log. They may be in the +# message to get a bit of space between the message and the command line +# prompt. Lines may be empty, so try. + try: + while len(Lines[-1]) == 0: + Lines = Lines[:-1] + Fp = open(MSGspec, "a") + for Line in Lines: +# Also no beeping in the log. + Line = Line.replace("\a", "") + if Line != "": + if Time == False: + Fp.write("%s\n"%Line) + else: + Fp.write("%s %s\n"%(getGMT(3), Line)) + else: + Fp.write("\n") + Fp.close() + except IndexError: + pass return # END: logIt -########################################## -# BEGIN: readFileLines2(Fp, Strip = False) -# LIB:readFileLines2():2019.052 -# This is like reafFileLines() in that the file has to be opened for it, but -# is also like readFileLinesRB() in the way it splits the lines. It is -# for things like URL openings and such that may or may not be allowed to -# be opened "rb". -# Reads the passed file and returns a List of lines after determining how to -# split the lines which may end differently depending on which operating -# system the file was written by. -# A List of continuous text may also be passed as Fp and that will be split -# into a List of lines. -# Since this does a split on the delimiters the lines returned will not have -# \r\n or \r or \n at the end. -# It also removes trailing whitespace from the lines. -# The file should be opened with "r", not "rb", by the caller. -def readFileLines2(Fp, Strip = False): +# First day of the month for each non-leap year month MINUS 1. This will get +# subtracted from the DOY, so a DOY of 91, minus the first day of April 90 +# (91-90) will leave the 1st of April. The 365 is the 1st of Jan of the next +# year. +PROG_FDOM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) + +############################# +# BEGIN: q330yd2md(YYYY, DOY) +# LIB:q330yd2md():2013.023 +# Converts YYYY,DOY to Month, Day. Faster than using using ydhmst2Time(). +# Expects a 4-digit YYYY. +def q330yd2md(YYYY, DOY): + if DOY < 1 or DOY > 366: + return 0, 0 + if DOY < 32: + return 1, DOY + elif DOY < 60: + return 2, DOY-31 + if YYYY%4 != 0: + Leap = 0 + elif YYYY%100 != 0 or YYYY%400 == 0: + Leap = 1 + else: + Leap = 0 +# Check for this special day. + if Leap == 1 and DOY == 60: + return 2, 29 +# The PROG_FDOM values for Mar-Dec are set up for non-leap years. If it is a +# leap year and the date is going to be Mar-Dec (it is if we have made it this +# far), subtract Leap from the day. + DOY -= Leap +# We start through PROG_FDOM looking for dates in March. + Month = 3 + for FDOM in PROG_FDOM[4:]: +# See if the DOY is less than the first day of next month. + if DOY <= FDOM: +# Subtract the DOY for the month that we are in. + return Month, DOY-PROG_FDOM[Month] + Month += 1 + return 0, 0 +# END: q330yd2md + + + + +########################################################### +# BEGIN: readFileLinesRB(Filespec, Strip = False, Bees = 0) +# LIB:readFileLinesRB():2019.239 +# This is the same idea as readFileLines(), but the Filespec is passed and +# the file is treated as a 'hostile text file' that may be corrupted. This +# can be used any time, but was developed for reading Reftek LOG files which +# can be corrupted, or just have a lot of extra junk added by processing +# programs. +# This is based on the method used in rt72130ExtractLogData(). +# The return value is (0, [lines]) if things go OK, or a standard error +# message if not, except the "e" of the exception also will be returned +# after the passed Filespec, so the caller can construct their own error +# message if needed. +# If Bees is not 0 then that many bytes of the file will be returned and +# converted to lines. If Bees is less than the size of the file the last +# line will be discarded since it's a good bet that it will be a partial +# line. +# Weird little kludge: Setting Bees to -42 tells the function that Filespec +# contains a bunch of text and that it should be split up into lines and +# returned just as if the text had come from reading a file. +# If Filespec has "http:" or "https:" in it then urlopen() will be used. +def readFileLinesRB(Filespec, Strip = False, Bees = 0): Lines = [] - if isinstance(Fp, list) == False and isinstance(Fp, astring) == False: - if PROG_PYVERS == 2: - Raw = Fp.read() - elif PROG_PYVERS == 3: -# To avoid the b"". - Raw = Fp.read().decode("latin-1") -# A blob of loaded by the caller. - elif isinstance(Fp, astring): - Raw = Fp - if isinstance(Fp, list) == False and isinstance(Fp, astring) == False: + if Bees != -42: + try: + if Filespec.find("http:") == -1 and Filespec.find("https:") == -1: +# These should be text files, but there's no way to know if they are ASCII or +# Unicode or garbage, especially if they are corrupted, so open binarially. + Fp = open(Filespec, "rb") + else: + Fp = urlopen(Filespec) +# This will be trouble if the file is huge, but that should be rare. Bees can +# be used if the file is known to be yuge. This should result in one long +# string. This and the "rb" above seems to work on Py2 and 3. + if Bees == 0: + Raw = Fp.read().decode("latin-1") + else: + Raw = Fp.read(Bees).decode("latin-1") + Fp.close() + if len(Raw) == 0: + return (0, Lines) + except Exception as e: + try: + Fp.close() + except: + pass + return (1, "MW", "%s: Error opening/reading file.\n%s"% \ + (basename(Filespec), e), 3, Filespec, e) + else: + Raw = Filespec + Filespec = "PassedLines" # Yes, this is weird. These should be "text" files and in a non-corrupted file # there should be either all \n or all \r or the same number of \r\n and \n # and \r. Try and split the file up based on these results. - RN = Raw.count("\r\n") - N = Raw.count("\n") - R = Raw.count("\r") + RN = Raw.count("\r\n") + N = Raw.count("\n") + R = Raw.count("\r") # Just one line by itself with no delimiter? OK. - if RN == 0 and N == 0 and R == 0: - return (0, [Raw]) + if RN == 0 and N == 0 and R == 0: + return (0, [Raw]) # Perfect \n. May be the most popular, so we'll check for it first. - if N != 0 and R == 0 and RN == 0: - RawLines = Raw.split("\n") + if N != 0 and R == 0 and RN == 0: + RawLines = Raw.split("\n") # Perfect \r\n file. We checked for RN=0 above. - elif RN == N and RN == R: - RawLines = Raw.split("\r\n") + elif RN == N and RN == R: + RawLines = Raw.split("\r\n") # Perfect \r. - elif R != 0 and N == 0 and RN == 0: - RawLines = Raw.split("\r") - else: + elif R != 0 and N == 0 and RN == 0: + RawLines = Raw.split("\r") + else: # There was something in the file, so make a best guess based on the largest # number. It might be complete crap, but what else can we do? - if N >= RN and N >= R: - RawLines = Raw.split("\n") - elif N >= RN and N >= R: - RawLines = Raw.split("\r\n") - elif R >= N and R >= RN: - RawLines = Raw.split("\n") + if N >= RN and N >= R: + RawLines = Raw.split("\n") + elif N >= RN and N >= R: + RawLines = Raw.split("\r\n") + elif R >= N and R >= RN: + RawLines = Raw.split("\n") # If all of those if's couldn't figure it out. - else: - return (1, "RW", "%s: Unrecognized file format."% \ - basename(Filespec), 2, Filespec) -# The caller must have loaded the individual lines and passed them. - elif isinstance(Fp, list): - RawLines = Fp + else: + return (1, "RW", "%s: Unrecognized file format."% \ + basename(Filespec), 2, Filespec) +# If Bees is not 0 then throw away the last line if the file is larger than +# the number of bytes requested. + if Bees != 0 and Bees < getsize(Filespec): + RawLines = RawLines[:-1] # Get rid of trailing empty lines. They can sneak in from various places # depending on who wrote the file. Do the strip in case there are something # like leftover \r's when \n was used for splitting. @@ -1241,7 +2462,7 @@ def readFileLines2(Fp, Strip = False): if len(RawLines) == 0: return (0, Lines) # If the caller doesn't want anything else then just go through and get rid of -# any trailing whitespace, else get rid of all the whitespace. +# any trailing spaces, else get rid of all the spaces. if Strip == False: for Line in RawLines: Lines.append(Line.rstrip()) @@ -1249,7 +2470,7 @@ def readFileLines2(Fp, Strip = False): for Line in RawLines: Lines.append(Line.strip()) return (0, Lines) -# END: readFileLines2 +# END: readFileLinesRB @@ -1267,645 +2488,930 @@ def sP(Count, Phrases): -############################################# -# BEGIN: writeFilesTxt(Msg, Filesspec, Files) -# LIB:writeFilesTxt():2019.018 -def writeFilesTxt(Msg, Filesspec, Files): - global BState +######################################################## +# BEGIN: writeFilesTxt(MSGspec, TagID, Filesspec, Files) +# LIB:writeFilesTxt():2019.233 +# Writes the list of files obtained by getFilesHtm() to the passed Filespec. +def writeFilesTxt(MSGspec, TagID, Filesspec, Files): FilesCount = 0 FilesBytes = 0 try: Fp = open(Filesspec, "w") FilesCount = 0 + Fp.write("Files on baler %s at %s:\n"%(TagID, getGMT(3))) for File in Files: FilesCount += 1 - Fp.write("%d. %s %s to %s %d\n"%(FilesCount, File[FH_NAME], \ - File[FH_FROM], File[FH_TO], File[FH_SIZE])) - FilesBytes += File[FH_SIZE] + Fp.write("%d. %s %s to %s %d\n"%(FilesCount, File[B_NAME], \ + File[B_FROM], File[B_TO], File[B_SIZE])) + FilesBytes += File[B_SIZE] Fp.close() except Exception as e: - return (1, "MW", "Error writing file list.\n%s\nStopped. (%s)"%( e, \ - getGMT(3)), 3) - if BState == STATE_STOP: - return (1, "YB", "Stopped by user. (%s)"%getGMT(3), 2) - if Msg == "bg": - msgLn(1, "W", "Wrote file list to %s"%Filesspec) - msgLn(0, "", "%s %s, %s %s on baler."%(fmti(FilesCount), \ - sP(FilesCount, ("file", "files")), fmti(FilesBytes), \ - sP(FilesBytes, ("byte", "bytes")))) - elif Msg == "bl": - logIt("Wrote file list to %s"%Filesspec) - logIt("%s %s, %s %s on baler."%(fmti(FilesCount), sP(FilesCount, \ - ("file", "files")), fmti(FilesBytes), sP(FilesBytes, \ - ("byte", "bytes")))) + return (1, "MW", "Error writing file list.\n%s"%e, 3) + logIt(MSGspec, "Wrote file list to: %s"%Filesspec) + logIt(MSGspec, "%s %s, %s %s on baler."%(fmti(FilesCount), sP(FilesCount, \ + ("file", "files")), fmti(FilesBytes), sP(FilesBytes, ("byte", \ + "bytes")))) return (0, FilesCount, FilesBytes) # END: writeFilesTxt -# First day of the month for each non-leap year month MINUS 1. This will get -# subtracted from the DOY, so a DOY of 91, minus the first day of April 90 -# (91-90) will leave the 1st of April. The 365 is the 1st of Jan of the next -# year. -PROG_FDOM = (0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) - -############################# -# BEGIN: q330yd2md(YYYY, DOY) -# LIB:q330yd2md():2013.023 -# Converts YYYY,DOY to Month, Day. Faster than using using ydhmst2Time(). -# Expects a 4-digit YYYY. -def q330yd2md(YYYY, DOY): - if DOY < 1 or DOY > 366: - return 0, 0 - if DOY < 32: - return 1, DOY - elif DOY < 60: - return 2, DOY-31 - if YYYY%4 != 0: - Leap = 0 - elif YYYY%100 != 0 or YYYY%400 == 0: - Leap = 1 - else: - Leap = 0 -# Check for this special day. - if Leap == 1 and DOY == 60: - return 2, 29 -# The PROG_FDOM values for Mar-Dec are set up for non-leap years. If it is a -# leap year and the date is going to be Mar-Dec (it is if we have made it this -# far), subtract Leap from the day. - DOY -= Leap -# We start through PROG_FDOM looking for dates in March. - Month = 3 - for FDOM in PROG_FDOM[4:]: -# See if the DOY is less than the first day of next month. - if DOY <= FDOM: -# Subtract the DOY for the month that we are in. - return Month, DOY-PROG_FDOM[Month] - Month += 1 - return 0, 0 -# END: q330yd2md +############################################### +# BEGIN: logHeader(MSGspec, Which, Time = True) +# FUNC:logHeader():2019.254 +def logHeader(MSGspec, Which, Time = True): + logIt(MSGspec, " ", False) + logIt(MSGspec, "Command: %s"%args2SL("s", 0), Time) + if "1" in Which: + logIt(MSGspec, "%s %s"%(PROG_NAME, PROG_VERSION), Time) + if "2" in Which: + logIt(MSGspec, "Python version: %s"%PROG_PYVERSION, Time) + if "3" in Which: + logIt(MSGspec, "Working directory: %s"%ABSCWDspec, Time) + logIt(MSGspec, "Writing messages to: %s"%MSGspec, Time) + return +# END: logHeader -############################## -# BEGIN: checkDTFile(Filespec) -# FUNC:checkDTFile():2019.018 -# Reads the mini-seed file block headers to see if they are corrupted. -# Note Ret[0] values, so the caller can keep tabs. Also the return value -# has the file timerange added to the end. -def checkDTFile(Filespec): - Iam = stack()[0][3] -# Keep a list of station IDs that we come across. Should be just one. - StationIDs = [] -# Keep a list of the channel names we find (should be just one for balers). - Channels = [] -# The caller should be smarter than this...but maybe not. - if isdir(Filespec): - return (8, "RW", "File %s is a directory."%basename(Filespec), 2, "", \ - "") - try: - Fp = open(Filespec, "rb") - except Exception as e: - return (1, "MW", str(e).strip(), 3, "", "") -# The standard record size for baler files is 4K. Read one and determine if -# it is smaller. - Record = Fp.read(4096+256) - RecordLen = len(Record) - if RecordLen == 0: - return (0, "GB", "File empty.", 0, "", "") - StaID = Record[8:13].decode("latin-1") - if StaID == Record[8+256:13+256].decode("latin-1"): - RecordSize = 256 - elif StaID == Record[8+512:13+512].decode("latin-1"): - RecordSize = 512 - elif StaID == Record[8+1024:13+1024].decode("latin-1"): - RecordSize = 1024 - elif StaID == Record[8+2048:13+2048].decode("latin-1"): - RecordSize = 2048 - elif StaID == Record[8+4096:13+4096].decode("latin-1"): - RecordSize = 4096 - else: - return (1, "MW", "Could not determine record size.", 3, "", "") - Fp.seek(0) -# Read the file in 10 record chunks to make it faster. - RecordsSize = RecordSize*10 - RecordsRead = 0 - FirstTime = "Z" - LastTime = "" -# Some may use this for messages. - NoOfRecords = int(getsize(Filespec)/RecordSize) - while 1: - Records = Fp.read(RecordsSize) -# We're done with this file. - if len(Records) == 0: - break - RecordsRead += 10 - for i in arange(0, 10): - Ptr = RecordSize*i - Record = Records[Ptr:Ptr+RecordSize] -# Need another set of Records. - if len(Record) < RecordSize: - break - ChanID = Record[15:18].decode("latin-1") -# The Q330 may create an "empty" file (all 00H) and then not finish filling it -# in. The read()s keep reading, but there's nothing to process. This detects -# that and returns. This may only happen in .bms-type data. - if ChanID == "\x00\x00\x00": -# I guess if a file is scrambled these could have 1000's of things in them. -# Both of them should only have one thing for baler files. Convert them to -# strings and then chop them off if they are long. - Stas = list2Str(StationIDs) - if len(Stas) > 12: - Stas = Stas[:12]+"..." - Chans = list2Str(Channels) - if len(Chans) > 12: - Chans = Chans[:12]+"..." - return (4, "YB", \ - "FILE ENDS EMPTY. Times: %s to %s\n Recs: %d(%d) Stas: %s Chans: %s"% \ - (FirstTime, LastTime, RecordsRead, RecordSize, Stas, \ - Chans), 2, FirstTime, LastTime) - if ChanID not in Channels: - Channels.append(ChanID) - StaID = Record[8:13].decode("latin-1").strip() - if StaID not in StationIDs: - StationIDs.append(StaID) - Year, Doy, Hour, Mins, Secs, Tttt= unpack(">HHBBBxH", \ - Record[20:30]) - Month, Date = q330yd2md(Year, Doy) - DateTime = "%d-%02d-%02d %02d:%02d"%(Year, Month, Date, Hour, Mins) - if DateTime < FirstTime: - FirstTime = DateTime - if DateTime > LastTime: - LastTime = DateTime - Fp.close() - if RecordsRead == 0: - return (5, "YB", "NO DATA.", 2, "", "") -# Same as above. - Stas = list2Str(StationIDs) - if len(Stas) > 12: - Stas = Stas[:12]+"..." - Chans = list2Str(Channels) - if len(Chans) > 12: - Chans = Chans[:12]+"..." - if len(StationIDs) > 1: - return (6, "MB", \ - "MULTIPLE STATIONS. Times: %s to %s\n Recs: %d(%d) Stas: %s Chans: %s"% \ - (FirstTime, LastTime, RecordsRead, RecordSize, Stas, Chans), \ - 3, FirstTime, LastTime) - elif len(Channels) > 1: - return (7, "MB", \ - "MULTIPLE CHANNELS. Times: %s to %s\n Recs: %d(%d) Stas: %s Chans: %s"% \ - (FirstTime, LastTime, RecordsRead, RecordSize, Stas, Chans), \ - 3, FirstTime, LastTime) - else: - return (0, "GB", \ - "FILE OK. Times: %s to %s\n Recs: %d(%d) Stas: %s Chans: %s"% \ - (FirstTime, LastTime, RecordsRead, RecordSize, Stas, Chans), 1, \ - FirstTime, LastTime) -# END: checkDTFile +############### +# BEGIN: main() +# FUNC:main():2019.297 +# These lovely items are brought to you by Microsoft. Python 3 was keeping +# ' -#' for arguments, "" as an argument, or not passing command line +# arguments at all. It's related to the registry value +# HKEY_CLASSES_ROOT\Applications\python.exe\shell\open\command +# and not having a %* (no quotes around it) at the end of the Data command +# to get Python to pass command line arguments when using 'bline.py <args>' +# to start the program. 'python bline.py <args>' seems OK. It's a Python +# installation problem. Don't know when all of this started. It was fine Dec +# 2018. Feb 2019 was not. It might be an older bug that has shown back up. +# This goes through the arguments and cleans them up. +argv2 = [] +Index = 0 +for Arg in argv: + argv[Index] = argv[Index].strip() + if len(argv[Index]) != 0: + argv2.append(argv[Index]) + Index += 1 +argv = argv2 + +CWDspec = ".%s"%sep +ABSCWDspec = abspath(CWDspec) +if ABSCWDspec.endswith(sep) == False: + ABSCWDspec += sep +SDRspec = "" +MSGspec = "" +SETspec = "" +FDev = "" +FIPAddr = "" +FPNumber = 0 +CLTagID = "" + +# At least say something! +if len(argv) == 1: + helpVShort() + exit(0) +# Import Central. The "new" commands need non-standard Python modules. If any +# of these commands are used I want the program to list all of the modules +# that are not installed for any of these commands, or just go ahead and +# import them. The 'IP supplied' version of the -b command, and the -c command +# throw a bit of a monkey wrench into keeping this simple. +if "-b" in argv or "-c" in argv or "-s" in argv or "-x" in argv: + if len(argv) == 4 and argv[2] == "-b" and isIPV4Addr(argv[3]) == True: + pass + elif len(argv) == 2 and argv[1] == "-c": + pass + else: + OK = True + try: + from psutil import net_if_addrs + except ImportError: + logIt("", \ + "The module 'psutil' will need to be installed on this system.", \ + False) + OK = False + try: + from ipaddress import IPv4Network, ip_interface + except ImportError: + logIt("", \ + "The module 'ipaddress' will need to be installed on this system.", \ + False) + OK = False + if PROG_PYVERS == 2: + try: + import subprocess32 as sb + except ImportError: + logIt("", \ + "The module 'subprocess32' needs to be installed on this system.", \ + False) + OK = False + elif PROG_PYVERS == 3: + import subprocess as sb +# This is the only OS that will make use of this. + if PROGSystem == "lin": + try: + from pexpect import spawn + except ImportError: + logIt("", \ + "The module 'pexpect' will need to be installed on this system.", \ + False) + OK = False + if OK == False: + logIt("", "", False) + exit(1) +# I'm repeating this check for -n, because this may be useful without using +# -b,-c,-s,-x in the BaleAddr mode. There's no need to make the user install +# all of those other modules. +if "-n" in argv: + try: + from psutil import net_if_addrs + except ImportError: + logIt("", \ + "The module 'psutil' will need to be installed on this system.\n", \ + False) + exit(1) +#========== +# The simple bline.py <command> commands. +#========== +#===== bline.py -# =====# +#===== bline.py -a =====# +#===== bline.py -h =====# +#===== bline.py -H =====# +#===== bline.py -U =====# +#===== bline.py -UD =====# +#===== bline.py -c =====# -############### -# BEGIN: main() -# FUNC:main():2019.064 -# All of the action takes place here in one pass using if()'s. -FH_NAME = 0 -FH_SIZE = 1 -FH_FROM = 2 -FH_TO = 3 -Dirspec = ".%s"%sep -AbsDirspec = abspath(Dirspec) -# LATER. -#if ArgBader == True: -# Ret = address_balers() -# logIt(Ret) -# exit(0) -if AbsDirspec.endswith(sep) == False: - AbsDirspec += sep -if ArgUCheck == True: - stdout.write("%s %s\n"%(PROG_NAME, PROG_VERSION)) +# These commands are pretty straightforward and don't require any current +# directory, file or address information. +if "-#" in argv: + logIt("", "%s"%PROG_VERSION, False) + exit(0) +if argv[1] == "-a": + logIt("", "", False) + logIt("", PROG_LONGNAME, False) + logIt("", "Version %s"%PROG_VERSION, False) + logIt("", "PASSCAL Instrument Center", False) + logIt("", "Socorro, New Mexico USA", False) + logIt("", "", False) + logIt("", "Email: passcal@passcal.nmt.edu", False) + logIt("", "Phone: 575-835-5070", False) + logIt("", "", False) + logIt("", "Python: %s\n"%PROG_PYVERSION, False) + exit(0) +if argv[1] == "-c": + logHeader("", "1") + logIt("", "Control computer host name: %s"%socket.gethostname(), False) + logIt("", "Contol computer IP address: %s (maybe)\n"% \ + socket.gethostbyname(socket.gethostname()), False) + exit(0) +if "-h" in argv: + helpShort() + exit(0) +if "-H" in argv: + helpLong() + exit(0) +if "-n" in argv: + logIt("", "\nEnabled network device/interface names on this system:", \ + False) + Nis = net_if_addrs() + Keys = list(Nis.keys()) + Keys.sort() + for Ni in Keys: + logIt("", " %s"%Ni, False) + logIt("", "", False) + exit(0) +if "-U" in argv: + logHeader("", "1", False) Ret = checkForUpdates("check") exit(Ret) -if ArgUDown == True: +if "-UD" in argv: + logHeader("", "1", False) Ret = checkForUpdates("get") exit(Ret) -DirspecSDR = ".%s%s.sdr%s"%(sep, TagID, sep) -Msgspec = Dirspec+TagID+".msg" -# Duplicating some stuff so the messages come out the way I want them to. -if exists(Msgspec) == False: - Fp = open(Msgspec, "w") - Fp.close() - if WriteMsg == True: - logIt(TheMessage) +# There are no more short 'bline -?' commands beyond this point. +if len(argv) < 3: + logIt("", "What??\a\n", False) + exit(1) + +# Everything from here on uses the command line TagID in the first position, so +# make sure the user has entered what looks like a valid TagID. +CLTagID = argv[1] +# Zeros are on the baler tags, but I don't think any software uses them. +while CLTagID.startswith("0"): + CLTagID = CLTagID[1:] +if len(CLTagID) == 0: + logIt("", "TagID was just zeros?\a\n", False) + exit(1) +try: + Value = int(CLTagID) +except: + logIt("", "TagID '%s' must be just numbers.\a\n"%CLTagID, False) + exit(1) +# Now make these. Some commands will use some of these and some won't. +SDRspec = ".%s%s.sdr%s"%(sep, CLTagID, sep) +MSGspec = CWDspec+CLTagID+".msg" +SETspec = CWDspec+CLTagID+".set" +TXTFspec = CWDspec+CLTagID+"files.txt" + +# These commands just take care of themselves and then quit. +#===== bline.py <tagid> -A =====# +#===== bline.py <tagid> -c =====# +#===== bline.py <tagid> -G =====# +#===== bline.py <tagid> -GD =====# +#===== bline.py <tagid> -M <message> =====# + +if argv[2] == "-A": + logHeader(MSGspec, "") + Files = glob("%s*__.*"%SDRspec) + if len(Files) == 0: + logIt(MSGspec, "No offloaded files found.") + logIt(MSGspec, "Are you in the directory above the .sdr directory?\n") exit(0) - logIt("%s %s"%(PROG_NAME, PROG_VERSION)) - logIt("Command:%s"%args2Str(0)) - logIt("Working directory: %s"%AbsDirspec) - logIt("Created %s"%Msgspec) -else: - if WriteMsg == True: - logIt(TheMessage) + logIt(MSGspec, "Files to copy: %d"%len(Files)) +# Get the list of channels from the offloded files. + Chans = [] + for File in Files: + Chan = File[-4:] + if Chan not in Chans: + Chans.append(Chan) + Chans.sort() + Files.sort() + from shutil import copyfileobj + Count = 0 + try: + OutFile = "%s%s.ALL"%(SDRspec, CLTagID) + FpA = open(OutFile, "wb") + logIt(MSGspec, "Copying files to %s..."%OutFile) + for Chan in Chans: + for File in Files: + if File.endswith(Chan): + Count += 1 + logIt(MSGspec, " %d. Copying file %s..."%(Count, \ + basename(File)), False) + Fp = open(File, "rb") + copyfileobj(Fp, FpA, -1) + Fp.close() + FpA.close() + except KeyboardInterrupt: + try: + Fp.close() + except: + pass + try: + FpA.close() + except: + pass + logIt(MSGspec, "Stopped by user.") exit(0) - logIt("%s %s"%(PROG_NAME, PROG_VERSION)) - logIt("Command:%s"%args2Str(0)) - logIt("Working directory: %s"%AbsDirspec) - logIt("Using %s"%Msgspec) -# Hijack and detour the program for this. -if ArgBadBlocks == True: - if exists(DirspecSDR) == False: - logIt("Done checking. No files have been offloaded.") - logIt("") + logIt(MSGspec, "Finished.") + exit(0) + + +# This is a second form of the -c command that will use the actual Ethernet +# device for the entered baler if BLINE's -b command was used to set the baler +# address. +if argv[2] == "-c": + logHeader(MSGspec, "") +# It has to call this here, because the community call is below this. + Ret = getSetSettings(SETspec, "get") + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + FDev = Ret[1] + if FDev == "": + logIt(MSGspec, "%s.set file does not contain the Ethernet device.\n"% \ + CLTagID) exit(0) - DFiles = listdir(DirspecSDR) - DFiles.sort() - FilesChecked = 0 - FilesOK = 0 - FilesOKSize = 0 -# These get loaded with the file names depending on the Ret[0] value coming -# from checkDTFile() for a summary. - FilesOpenErrors1 = [] - FilesTooSmall2 = [] - FilesRecSize3 = [] - FilesEndEmpty4 = [] - FilesNoData5 = [] - FilesMultStas6 = [] - FilesMultChans7 = [] - FilesIsDir8 = [] -# For an overall baler time range. - FirstFilesTime = "Z" - LastFilesTime = "" -# Loop through DFiles and then through all of the file(s) the user specified -# looking for matches. - for Index in arange(0, len(DFiles)): - DFile = DFiles[Index] - Matches = False - for ArgSpecFile in ArgSpecFiles: - if fnmatch(DFile, ArgSpecFile) == True: - Matches = True - break - if Matches == False: - continue - DFilespec = DirspecSDR+DFile -# checkDTFile() will also catch this. We'll still look for Rec[0]==8. Non-data -# files will probably come back with a Rec Size error. - if isdir(DFilespec): - continue - FilesChecked += 1 - logIt("%d. Checking %s..."%(FilesChecked, DFilespec)) - Ret = checkDTFile(DFilespec) - logIt(" "+Ret[2]) - FirstTime = Ret[4] - LastTime = Ret[5] -# If something goes wrong the returned times will be "". - if FirstTime != "" and FirstTime < FirstFilesTime: - FirstFilesTime = FirstTime - if LastTime != "" and LastTime > LastFilesTime: - LastFilesTime = LastTime - if Ret[0] == 0: - FilesOK += 1 - FilesOKSize += getsize(DFilespec) - elif Ret[0] == 1: - FilesOpenErrors1.append(DFile) - elif Ret[0] == 2: - FilesTooSmall2.append(DFile) - elif Ret[0] == 3: - FilesRecSize3.append(DFile) - elif Ret[0] == 4: - FilesEndEmpty4.append(DFile) - elif Ret[0] == 5: - FilesNoData5.append(DFile) - elif Ret[0] == 6: - FilesMultStas6.append(DFile) - elif Ret[0] == 7: - FilesMultChans7.append(DFile) - elif Ret[0] == 8: - FilesIsDir8.append(DFile) - logIt("Done checking.") - logIt("Summary:") - logIt("Overall date range: %s to %s"%(FirstFilesTime, LastFilesTime)) - logIt(" Files checked: %d"%FilesChecked) - logIt(" Files OK: %d (%s %s)"%(FilesOK, fmti(FilesOKSize), \ - sP(FilesOKSize, ("byte", "bytes")))) - if len(FilesOpenErrors1) != 0: - logIt("File opening errors: %d"%len(FilesOpenErrors1)) - Count = 0 - for File in FilesOpenErrors1: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesTooSmall2) != 0: - logIt("Files too small: %d"%len(FilesTooSmall2)) - Count = 0 - for File in FilesTooSmall2: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesRecSize3) != 0: - logIt("Unknown record size: %d"%len(FilesRecSize3)) - Count = 0 - for File in FilesRecSize3: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesEndEmpty4) != 0: - logIt("Ending empty: %d"%len(FilesEndEmpty4)) - Count = 0 - for File in FilesEndEmpty4: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesNoData5) != 0: - logIt("No data: %d"%len(FilesNoData5)) - Count = 0 - for File in FilesNoData5: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesMultStas6) != 0: - logIt("Multiple stations: %d"%len(FilesMultStas6)) - Count = 0 - for File in FilesMultStas6: - Count += 1 - logIt(" %d. %s"%(Count, File)) - if len(FilesMultChans7) != 0: - logIt("Multiple channels: %d"%len(FilesMultChans7)) - Count = 0 - for File in FilesMultChans7: - Count += 1 - logIt(" %d. %s"%(Count, File)) - elif len(FilesIsDir8) != 0: - logIt("Directories: %d"%len(FilesIsDir8)) + Ret = ipFromDevice(FDev) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(0) + IPMask = Ret[1] + IP, Mask = ("%s"%IPMask).split("/") + logIt(MSGspec, "Control computer Ethernet device: %s"%FDev) + logIt(MSGspec, "Control computer IP address: %s"%IP) + logIt(MSGspec, "Control computer netmask: %s\n"%bitsToNetmask(Mask)) + exit(0) + +if argv[2] == "-G" or argv[2] == "-GD": + from shutil import copyfileobj + from struct import unpack + logHeader(MSGspec, "") + if CLTagID != "9999": +# Make a TagIDDirs entry to fool the code below. + TagIDDirs = [".%s%s.sdr"%(sep, CLTagID)] + elif CLTagID == "9999": + TagIDDirs = glob(".%s*.sdr"%sep) + if len(TagIDDirs) == 0: +# ID 9999.msg will be fine. + logIt(MSGspec, "No offloaded files found.") + logIt(MSGspec, \ + "Are you in the directory above the .sdr directories?\n") + exit(0) + for TagID in TagIDDirs: + TagID = TagID.split(".sdr")[0] + TagID = TagID[2:] +# Rebuild these for each TagID. Won't be necessary for -GD, but... + SDRspec = ".%s%s.sdr%s"%(sep, TagID, sep) + if argv[2] == "-G": + DATAspec = SDRspec + elif argv[2] == "-GD": + DATAspec = ".%sDATA%s"%(sep, sep) + if exists(DATAspec) == False: + makedirs(DATAspec) +# We'll be kinda specific so we don't have to keep filtering below. + Files = glob("%s*__.*"%SDRspec) + if len(Files) == 0: + logIt(MSGspec, "Looking in %s"%SDRspec) + logIt(MSGspec, "No offloaded files found.") + logIt(MSGspec, \ + "Are you in the directory above the .sdr directory?\n") + exit(0) +# Get the list of channels from the offloded files. + Chans = [] + for File in Files: + Chan = File[-4:] + if Chan not in Chans: + Chans.append(Chan) + logIt(MSGspec, "Channels to group: %d"%len(Chans)) + Chans.sort() + Files.sort() Count = 0 - for File in FilesIsDir8: - Count += 1 - logIt(" %d. %s"%(Count, File)) - logIt("") + try: + for Chan in Chans: +# We need to go through the channel files, find the first one, open it and +# extract the station name, net code, etc. for the file name. If this set of +# data files has multiple of any of the file name items then all bets are off. +# This only reads the first header. + for File in Files: + if File.endswith(Chan): + Fp = open(File, "rb") + Header = Fp.read(128) + Fp.close() + if PROG_PYVERS == 2: + Qual = Header[6] + elif PROG_PYVERS == 3: + Qual = chr(Header[6]) + StaID = Header[8:13].strip().decode("latin-1") + LocID = Header[13:15].strip().decode("latin-1") + ChanID = Header[15:18].strip().decode("latin-1") + NetCode = Header[18:20].strip().decode("latin-1") + Year, Doy, Hour, Mins, Secs, \ + Tttt= unpack(">HHBBBxH", Header[20:30]) + STime = "%s.%03d.%02d%02d%02d"%(Year, Doy, Hour, \ + Mins, Secs) +# This is [another] one of many versions of file names. + OutFile = "%s%s.%s.%s.%s.%s.%s"%(DATAspec, StaID, \ + NetCode, LocID, ChanID, Qual, STime) + FpG = open(OutFile, "wb") + break + Count += 1 + logIt(MSGspec, " %d. Copying files to %s"%(Count, OutFile), \ + False) + for File in Files: + if File.endswith(Chan): + Fp = open(File, "rb") + copyfileobj(Fp, FpG, -1) + Fp.close() + FpG.close() + except KeyboardInterrupt: + try: + Fp.close() + except: + pass + try: + FpG.close() + except: + pass + logIt(MSGspec, "Stopped by user.") + exit(0) + logIt(MSGspec, "Finished.") + exit(0) + +# The user may want to put a message into the .msg file for the baler before +# doing anything else (service run info, site ID, etc.), so check for this +# command before the exists() checks below. This will get the .msg file +# created if it does not exist. +if argv[2] == "-M": + CLMessage = args2SL("s", 3) + if CLMessage == "": + logIt("", "No -M message entered.\a\n", False) + beep() + exit(1) + logIt(MSGspec, "\n%s\n"%CLMessage) exit(0) -# All other commands. -# Get the basic info and make sure we are all on the same page. Do this each -# time we do anything. -Ret = getBalerHtm("bl", TagID, IPAddr) -if Ret[0] != 0: - logIt(Ret[2]) - logIt("") + +#========== Now do the rest of the setups ==========# + +# The -b command is used to find balers and save IP addresses, so skip this +# check for that command. +if argv[2] != "-b" and exists(SDRspec) == False and \ + exists(MSGspec) == False and exists(SETspec) == False: + logIt(MSGspec, "TagID '%s' is unknown.\a\n"%CLTagID) exit(1) -# Might as well check for this before we go to the trouble of getting the list -# of files (which could take a long time). -if ArgVerify == True: - if exists(DirspecSDR) == False: - logIt("Done verifying. No files have been offloaded.") - logIt("") + +# Commands that expect there to be offloaded files. There may still be a +# directory, but no files. The commands will have to handle that. +if argv[2] in ("-v", "-vl", "-V"): + if exists(SDRspec) == False: + logIt("", "No baler files have been offloaded.\n", False) exit(0) -FWVers = Ret[1] -DSize = Ret[2] -NFiles = Ret[3] -Percent = Ret[4] -if ArgCheck == True: - Used = (Percent/100.0)*DSize - logIt("FWVers: %s DiskSize: %s"%(FWVers, fmti(DSize))) - logIt("%.1f%% of %s %s used."%(Percent, fmti(NFiles), sP(NFiles, \ - ("file", "files")))) - logIt("Done checking.") - logIt("") - exit(0) -# A List of Lists of the data files on the baler according to files.htm. -FHFiles = [] + +# The commands that need a simple header before the other prep stuff below. +if argv[2] in ("-L", "-s", "-x"): + logHeader(MSGspec, "") + +# The commands that need a little more header info. +if argv[2] in ("-e", "-E", "-F", "-i", "-l", "-o", "-O", "-v", "-vl", "-V"): + logHeader(MSGspec, "3") + +# Get what should be the baler's comm info for these commands. +if argv[2] in ("-e", "-E", "-F", "-i", "-l", "-o", "-O", "-s", "-v", "-vl", \ + "-x"): + Ret = getSetSettings(SETspec, "get") + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + FDev = Ret[1] + FIPAddr = Ret[2] + FPNumber = intt(Ret[3]) + +# Throw this check in here, since there will be no reason to continue. + if argv[2] in ("-s", "-x"): + if FDev == "" or FPNumber == 0: + logIt(MSGspec, \ + "%s.set file does not contain the Ethernet device/port"% \ + CLTagID) + logIt(MSGspec, "information required for this command.\n") + exit(0) + +# The commands that might use a list of command line files even just *. +if argv[2] in ("-o", "-O", "-v", "-vl", "-V"): + IFiles = args2SL("l", argv[2]) + if len(IFiles) == 0: + IFiles.append("*") + +# Commands that have to have some command line files specified and not just *. +if argv[2] in ("-e", "-E", "-F"): + IFiles = args2SL("l", argv[2]) + if len(IFiles) == 0: + logIt(MSGspec, "No %s files specified.\n"%argv[2]) + exit(1) + if "*" in IFiles: + logIt(MSGspec, "The wildcard * is not allowed for this command.") + exit(1) + +# Check that the entered TagID and IP address go together and try to get the +# general info from the baler. +if argv[2] in ("-e", "-E", "-F", "-i", "-l", "-o", "-O", "-v", "-vl"): + logIt(MSGspec, "Getting baler info from %s at %s..."%(CLTagID, FIPAddr)) + try: + Ret = getBalerInfoHtm(CLTagID, FIPAddr) + except KeyboardInterrupt: + logIt(MSGspec, "Stopped by user.") + exit(0) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + BInfo = Ret[1] + logIt(MSGspec, "Got baler info.") + +# A simpler verification check than above. This also gets done by using +# getBalerInfoHtm() above, so you don't need to call this too. +if argv[2] in ("-s", "-x"): + try: + Ret = checkTagID(CLTagID, FIPAddr) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + except KeyboardInterrupt: + logIt(MSGspec, "Stopped by user.") + exit(0) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + +# The commands that use the list of files from the baler. +if argv[2] in ("-F", "-e", "-E", "-l", "-o", "-O", "-v", "-vl"): + BFiles = [] # files.htm can take a long time to build (it's made and sent on-the-fly). -if Percent < 50: - logIt("Getting files.htm...") -else: - logIt("Getting files.htm...(could take a while)") -Ret = getFilesHtm("", TagID, IPAddr) -if Ret[0] != 0: - logIt(Ret[2]) - logIt("") - exit(1) -logIt("Got files.htm.") -FHFiles = Ret[1] -# Always write the whole list out to the file ./<TagID>files.htm. -Ret = writeFilesTxt("bl", Dirspec+TagID+"files.txt", FHFiles) -if Ret[0] != 0: - logIt(Ret[2]) - logIt("") - exit(1) -if ArgList == True: + if BInfo["Percent"] < 50: + logIt(MSGspec, "Getting files.htm from %s..."%CLTagID) + else: + logIt(MSGspec, "Getting files.htm from %s...(could take a while)"% \ + CLTagID) + try: + Ret = getFilesHtm(MSGspec, CLTagID, FIPAddr) + except KeyboardInterrupt: + logIt(MSGspec, "Stopped by user.") + exit(0) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + BFiles = Ret[1] + logIt(MSGspec, "Got files.htm.") +# Always write the whole list out since we were able to get it. + Ret = writeFilesTxt(MSGspec, CLTagID, TXTFspec, BFiles) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(1) + +# Commands that want the BFiles list (obtained with getFilesHtm()) pruned down +# to just the lower sample rate files. +if argv[2] in ("-e", "-o"): + for Index in arange(0, len(BFiles)): + try: + BName = BFiles[Index][B_NAME] + if isHigh(BName) == True: +# print("Removed from download list: %s"%BName) + BFiles[Index] = [] + except IndexError: + continue + +# Commands that want the BFiles list to only have matching entries with the +# command line specified files. +if argv[2] in ("-F", "-o", "-O", "-V"): +# If this is in there just leave BFiles the way it is. + if "*" not in IFiles: + for Index in arange(0, len(BFiles)): + if len(BFiles[Index]) == 0: + continue + Match = False + for IFile in IFiles: + if fnmatch(BFiles[Index][B_NAME], IFile) == True: + Match = True + break + if Match == False: + BFiles[Index] = [] + +# Commands that DON'T want matching command line specified files in the BFiles +# list. +if argv[2] in ("-e", "-E"): + for Index in arange(0, len(BFiles)): + if len(BFiles[Index]) == 0: + continue + Match = False + for IFile in IFiles: + if fnmatch(BFiles[Index][B_NAME], IFile) == True: + Match = True + break + if Match == True: + BFiles[Index] = [] + +#===== bline.py <tagid> -b [<ipaddr> | <ethdev>] =====# + +# Assign an IP address to the baler <tagid>. +# Create the <tagid>.set file with the <ipaddr> in it. +# Watch for the baler on the specified <ethdev>. +# If nothing is supplied the program will watch for a baler on all Ethernet +# devices...if it can. +# If the <ipaddr> is provided this will create/update the <tagid>.set file +# with the provided IP address for the rest of the HTML-based functions to +# use. There will be no Ethernet device or port number with the address, so +# the commands that need those (like -x) will not function. Use this if the +# IP address was set using something like BaleAddr. +# If the <ethdev> is supplied the address-assigning routine will be run, but +# only watching that device. Python 2, Windows, etc. do not have the +# socket.recvmsg() function, so they need to be told the device name. +if argv[2] == "-b": + if len(argv) == 3: + Ret = checkRPFiltersOff("") + if Ret[0] != 0: + logIt("", "%s\n"%Ret[1], False) + exit(1) + logHeader(MSGspec, "13") + logIt(MSGspec, "Watching for baler %s."%CLTagID) + logIt(MSGspec, "(Ctrl-C to stop before the baler is found.)") + logIt(MSGspec, "Press the ATTN button...") + try: + Ret = addressABaler(SETspec, MSGspec, CLTagID) + except KeyboardInterrupt: + logIt(MSGspec, "Stopped by user.") + exit(0) + logIt(MSGspec, Ret[2]) + logIt(MSGspec, "") + exit(0) + elif len(argv) == 4: +# If the passed item does not look like an IP address ass/u/me it is an +# Ethernet device, or that the user doesn't know what they are doing. + if isIPV4Addr(argv[3]) == True: + Ret = getSetSettings(SETspec, "set", "", argv[3], "") + if Ret[0] != 0: + logIt("", "%s\n"%Ret[2], False) + exit(1) + logHeader(MSGspec, "13") + logIt(MSGspec, "IP Address %s saved to file %s.set\n"%(argv[3], \ + CLTagID)) + exit(0) + else: +# This is almost the same as the len=3 section above. + Ret = checkRPFiltersOff("") + if Ret[0] != 0: + logIt("", "%s\n"%Ret[1], False) + exit(1) + logHeader(MSGspec, "13") + logIt(MSGspec, "Watching for baler %s on %s."%(CLTagID, argv[3])) + logIt(MSGspec, "(Ctrl-C to stop before the baler is found.)") + logIt(MSGspec, "Press the ATTN button...") + try: + Ret = addressABaler(SETspec, MSGspec, CLTagID, argv[3]) + except KeyboardInterrupt: + logIt(MSGspec, "Stopped by user.") + exit(0) + logIt(MSGspec, Ret[2]) + logIt(MSGspec, "") + exit(0) + +#===== bline.py <tagid> -x =====# + +# Sends the clean baler command. +if argv[2] == "-x": + logIt("", ">>> -x command will erase all data on the baler. <<<", False) +# Keep the original 'yes' answer for the log entry. + OAnswer = aninput("Continue to clean? (yes/no) ") + Answer = OAnswer.lower() + if Answer.startswith("y") == False: + logIt(MSGspec, "Nothing done.\n") + exit(0) + logIt(MSGspec, "Continue to clean? (yes/no) %s"%OAnswer) + Ret = cleanBaler(FDev, (FIPAddr, FPNumber)) + if Ret[0] != 0: + logIt(MSGspec, Ret[2]) + exit(1) + logIt(MSGspec, "Finished cleaning.\n") + exit(0) + +#===== bline.py <tagid> -i =====# + +if argv[2] == "-i": + if BInfo["NetSta"] == "": + BInfo["NetSta"] = "(none)" + logIt(MSGspec, "NetCode-StaID: %s"%BInfo["NetSta"]) + logIt(MSGspec, "Model: %s FWVersion: %s"%(BInfo["BModel"], \ + BInfo["FWVers"])) + logIt(MSGspec, "Disk Size: %s"%fmti(BInfo["DSize"])) + logIt(MSGspec, "%.1f%% of %s %s used."%(BInfo["Percent"], \ + fmti(BInfo["NFiles"]), sP(BInfo["NFiles"], ("file", "files")))) + logIt(MSGspec, "Temperature: %s"%BInfo["Temp"]) + logIt(MSGspec, "Supply voltage: %s"%BInfo["Volts"]) + logIt(MSGspec, "MAC address: %s"%BInfo["MACAddr"]) + logIt(MSGspec, "Disk model: %s"%BInfo["DModel"]) + logIt(MSGspec, "DMU2 FWVersion: %s"%BInfo["FW2Vers"]) + logIt(MSGspec, "Serial Number: %s\n"%BInfo["SerNum"]) + exit(0) + +#===== bline.py <tagid> -l =====# + +# Just lists the files on the baler according to files.htm. +if argv[2] == "-l": + logIt(MSGspec, "Baler %s file listing:"%CLTagID, False) + Count = 0 + for File in BFiles: + Count += 1 + logIt(MSGspec, " %d. %s %s to %s %d"%(Count, File[B_NAME], \ + File[B_FROM], File[B_TO], File[B_SIZE]), False) + logIt(MSGspec, "") + exit(0) + +#===== bline.py <tagid> -L =====# + +# Just lists in the <tagid>.sdr directory. +if argv[2] == "-L": + logIt(MSGspec, "Offloaded files in directory %s:"%SDRspec, False) + if exists(SDRspec) == False: + logIt(MSGspec, " Directory %s does not exist."%SDRspec, False) + exit(0) + Files = listdir(SDRspec) + Files.sort() + Count = 0 + for File in Files: + if File.startswith("."): + continue + Count += 1 + Size = getsize(SDRspec+File) + logIt(MSGspec, " %d. %s (%s %s)"%(Count, File, fmti(Size), \ + sP(Size, ("byte", "bytes"))), False) + if Count == 0: + logIt(MSGspec, " None", False) + logIt(MSGspec, "") + exit(0) + +#===== bline.py <tagid> -s =====# + +# Sends the shudown command to the baler. +if argv[2] == "-s": + Shutdown = Packet() + Shutdown.pack_shutdown() + Ret = ipFromDevice(FDev) + if Ret[0] != 0: + logIt(MSGspec, "%s\n"%Ret[2]) + exit(0) + localhost = Ret[1] + SockComm = socketComm(localhost.ip) + SockComm.sendto(Shutdown.buf, (FIPAddr, FPNumber)) + logIt(MSGspec, "Shutdown command sent. Watch the baler.\n") + exit(0) + +#==== bline.py <tagid> -V =====# + +# Goes through the offloaded files and checks to see that at least the +# mini-seed headers make sense...or don't. Does not need a baler connected. +if argv[2] == "-V": + Files = args2SL("l", "-V") + if len(Files) == 0: + Files.append("*") + badBlocks(MSGspec, SDRspec, Files, True) + logIt(MSGspec, "") + exit(0) + +#===== bline.py <tagid> -e -E -F -o -O [<files>] =====# + +# Offload the low sample rate files or all files that have not already been +# fully offloaded. BFiles has been trimmed down to the lower sample rate files +# in a previous section, and any matching or non-matching files depending on +# the command. So this just processes what's left in BFiles. +if argv[2] in ("-e", "-E", "-F", "-o", "-O"): +# Count the number of files we are really going to offload. The baler may be +# empty. + BToOffload = 0 + for BFile in BFiles: +# This file was a high sample rate file or not-requested file removed above. + if len(BFile) == 0: + continue + BToOffload += 1 + if BToOffload == 0: + logIt(MSGspec, "There are no requested baler files to offload.\n") + exit(0) +# Just make sure before we start checking for offloaded files. + if exists(SDRspec) == False: + makedirs(SDRspec) +# Now further reduce the number of files in BFiles by checking to see how many +# of them have already been fully offloaded. + BToOffload = 0 + BTotalSize = 0 +# How many already exist, but are not the right size. + OffExist = 0 + for Index in arange(0, len(BFiles)): + if len(BFiles[Index]) == 0: + continue + BName = BFiles[Index][B_NAME] + BSize = BFiles[Index][B_SIZE] + if exists(SDRspec+BName) == True: + if getsize(SDRspec+BName) == BSize: + BFiles[Index] = [] + continue + OffExist += 1 + BToOffload += 1 + BTotalSize += BSize + if BToOffload == 0: + logIt(MSGspec, \ + "All requested baler files have already been offloaded.\n") + exit(0) +# I'm just sayin'... + if OffExist != 0: + logIt("", " %d previously offloaded %s may be overwritten."% \ + (OffExist, sP(OffExist, ("file", "files"))), False) + Answer = aninput(" Is this OK? (yes/no): ") + Answer = Answer.strip().lower() + if Answer.startswith("y") == False: + logIt("", " Nothing done.", False) + exit(0) + logIt(MSGspec, "Saving data files to: %s"%SDRspec) +# MacBook was 40.2s, Linux Dell was 41.9s, old Win7 42.0s... + TTO = intt(BTotalSize/16777216.0*41.0/60.0) + if TTO == 0: + TTO = 1 + logIt(MSGspec, "Offloading %s %s, %s %s (~%s%s)..."% \ + (fmti(BToOffload), sP(BToOffload, ("file", "files")), \ + fmti(BTotalSize), sP(BTotalSize, ("byte", "bytes")), fmti(TTO), \ + sP(TTO, ("min", "mins")))) Count = 0 - for File in FHFiles: +# Files offloaded. + Offloaded = 0 +# Total bytes offloaded. + OffTotalSize = 0 +# Bytes from the current file offloaded. Gets updated by fileBlockRetrieved(). + OffBytes = 0 + for BFile in BFiles: + if len(BFile) == 0: + continue + OffBytes = 0 + Offloaded10 = 0 Count += 1 - stdout.write("%d. %s %s to %s %d\n"%(Count, File[FH_NAME], \ - File[FH_FROM], File[FH_TO], File[FH_SIZE])) - logIt("Done listing.") + BName = BFile[B_NAME] + BSize = BFile[B_SIZE] + if BSize == -1: + logIt(MSGspec, "%d/%d. Getting %s (?)..."%(Count, BToOffload, \ + BName)) + else: + logIt(MSGspec, "%d/%d. Getting %s (%s)..."%(Count, BToOffload, \ + BName, fmti(BSize))) + try: + try: + urlretrieve("http://%s/%s"%(FIPAddr, BName), "%s%s"%(SDRspec, \ + BName), fileBlockRetrieved) + except KeyboardInterrupt: + if Offloaded10 > 0: + stdout.write("\n") + logIt(MSGspec, "Stopped by user.\n") + exit(0) + except Exception as e: + if Offloaded10 > 0: + stdout.write("\n") + logIt(MSGspec, "ERROR: Retrieving file %s:"%BName) + logIt(MSGspec, "%s (got ~%s %s)\n"%(e, fmti(OffBytes), \ + sP(OffBytes, ("byte", "bytes")))) + exit(1) + OffSize = getsize(SDRspec+BName) + OffTotalSize += OffSize + if Offloaded10 > 0: + stdout.write(" (%.0f%%)\n"%(float(OffTotalSize)/BTotalSize*100)) + if BSize != -1: + if OffSize != BSize: + logIt(MSGspec, "ERROR: Size error. %s: Baler %s, File %s\n"% \ + (BName, fmti(BSize), fmti(OffSize))) + exit(1) + if OffSize < 4000: + logIt(MSGspec, "STRANGE: File %s is only %s %s."%(BName, \ + fmti(OffSize), sP(OffSize, ("byte", "bytes")))) + Offloaded += 1 + logIt(MSGspec, "Offloaded %s %s, %s %s.\n"%(fmti(Offloaded), \ + sP(Offloaded, ("file", "files")), fmti(OffTotalSize), \ + sP(OffTotalSize, ("byte", "bytes")))) exit(0) -# Go through FHFiles and match up by name and size the files that are already -# on the computer. Then go through the files in the directory and see if there + +#===== bline.py <tagid> -v | -vl [<files>] =====# + +# Go through BFiles and match up by name and size the files that are already +# on the computer, then go through the files in the directory and see if there # are any that don't belong there. -if ArgVerify == True: +if argv[2] in ("-v", "-vl"): Here = 0 HereSize = 0 NotHere = 0 - for Index in arange(0, len(FHFiles)): - Name = FHFiles[Index][FH_NAME] - Size = FHFiles[Index][FH_SIZE] - if exists(DirspecSDR+Name): - SDRSize = getsize(DirspecSDR+Name) - if SDRSize == Size: +# Make this copy so we can be destructive in case the command is -vl. + BFiles2 = deepcopy(BFiles) + for Index in arange(0, len(BFiles2)): + BName = BFiles2[Index][B_NAME] + BSize = BFiles2[Index][B_SIZE] + if exists(SDRspec+BName): + SDRSize = getsize(SDRspec+BName) + if SDRSize == BSize: Here += 1 HereSize += SDRSize +# Only keep the files in the list that are not here. + BFiles2[Index] = [] else: NotHere += 1 else: NotHere += 1 - logIt(" Data files fully offloaded: %s (%s %s)"%(fmti(Here), \ - fmti(HereSize), sP(HereSize, ("byte", "bytes")))) - logIt("Data files not fully offloaded: %s"%fmti(NotHere)) + logIt(MSGspec, " Data files fully offloaded: %s (%s %s)"% \ + (fmti(Here), fmti(HereSize), sP(HereSize, ("byte", "bytes"))), \ + False) + logIt(MSGspec, " Data files not fully offloaded: %s"%fmti(NotHere), \ + False) # List the files that have not been offloaded with (-vl command). - if ArgVerifyList == True: - logIt("Files not fully offloaded:") - NotHere = 0 - for Index in arange(0, len(FHFiles)): - Name = FHFiles[Index][FH_NAME] - Size = FHFiles[Index][FH_SIZE] - if exists(DirspecSDR+Name): - SDRSize = getsize(DirspecSDR+Name) - if SDRSize == Size: - pass - else: - NotHere += 1 - logIt("%d. %s (%s %s, short)"%(NotHere, Name, \ - fmti(SDRSize), sP(SDRSize, ("byte", "bytes")))) - else: - NotHere += 1 - logIt("%d. %s"%(NotHere, Name)) - DFiles = listdir(DirspecSDR) + if argv[2] == "-vl": + if NotHere > 0: + Count = 0 + for Index in arange(0, len(BFiles2)): + if len(BFiles2[Index]) > 0: + BName = BFiles[Index][B_NAME] + BSize = BFiles[Index][B_SIZE] + Count += 1 + logIt(MSGspec, " %d. %s (%s %s)"%(Count, BName, \ + fmti(BSize), sP(BSize, ("byte", "bytes"))), False) + elif NotHere == 0: + logIt(MSGspec, " All files fully offloaded.", False) +# Now the reverse check for files that don't belong in the .sdr. + DFiles = listdir(SDRspec) + logIt(MSGspec, " Files that do not belong in %s:"%SDRspec, False) Extras = 0 - FHFiles2 = [] - for FHFile in FHFiles: - if len(FHFile) == 0: - continue - FHFiles2.append(FHFile[FH_NAME]) for DFile in DFiles: - if DFile.startswith("."): + if len(DFile) == 0 or DFile.startswith("."): continue - if DFile not in FHFiles2: - if isdir(DirspecSDR+DFile): - logIt(" Directory in .%s%s.sdr: %s"%(sep, TagID, DFile)) - else: - Size = getsize(DirspecSDR+DFile) - logIt("Extra file in .%s%s.sdr: %s (%s %s)"%(sep, TagID, \ - DFile, fmti(Size), sP(Size, ("byte", "bytes")))) - Extras += 1 - if Extras > 0: - logIt("%s non-data file %s in .%s%s.sdr."%(fmti(Extras), \ - sP(Extras, ("item", "items")), sep, TagID)) - logIt("Done verifying.") - logIt("") - exit(0) -logIt("Offloading from baler %s @ %s"%(TagID, IPAddr)) -# Alter FHFiles as necessary and then fill up BFiles with the remains. -if ArgSpec == True: - logIt("Only offloading: %s"%list2Str(ArgSpecFiles)) -# Check each file against the command line argument(s). - for Index in arange(0, len(FHFiles)): - Matches = False - for ArgSpecFile in ArgSpecFiles: -# For entries that are already []. - try: - if fnmatch(FHFiles[Index][FH_NAME], ArgSpecFile) == True: - Matches = True - # Don't list them. There could be a lot. - break - except IndexError: - continue - if Matches == False: - FHFiles[Index] = [] -elif ArgSkipO == True or ArgSkipo == True: - logIt("Excluding: %s"%list2Str(ArgSpecFiles)) - for Index in arange(0, len(FHFiles)): - Matches = False - for ArgSpecFile in ArgSpecFiles: - try: - if fnmatch(FHFiles[Index][FH_NAME], ArgSpecFile) == True: - Matches = True - break - except IndexError: - continue - if Matches == True: - FHFiles[Index] = [] -# Because users and PASSCAL people are just too stupid. -for Index in arange(0, len(FHFiles)): -# FHFIles[Index] may be [], so try. - try: - Filename = FHFiles[Index][FH_NAME] - if len(Filename) != 0: - if exists(DirspecSDR+Filename): - logIt("Some offloaded files may overwrite existing files.") - logIt("Is this OK?") - if PROG_PYVERS == 2: - Answer = raw_input("Answer (yes/no): ") - elif PROG_PYVERS == 3: - Answer = input("Answer (yes/no): ") - Answer = Answer.strip().lower() - if Answer.startswith("n"): - logIt("Answer: N") - exit(1) - elif Answer.startswith("y"): - logIt("Answer: Y") - else: - logIt("Wrong answer.") - exit(1) + Found = False + for BFile in BFiles: + if DFile == BFile[B_NAME]: + Found = True break - except: - continue -# For any of the offloads always throw out files that are already here and -# that are the same size. -for Index in arange(0, len(FHFiles)): - try: - Filename = FHFiles[Index][FH_NAME] - Size = FHFiles[Index][FH_SIZE] - if exists(DirspecSDR+Filename) and \ - getsize(DirspecSDR+Filename) == Size: - FHFiles[Index] = [] - except: - continue -if ArgLSROffload == True or ArgSkipo == True: - logIt("Offloading low sample rate files.") - for Index in arange(0, len(FHFiles)): - try: - Filename = FHFiles[Index][FH_NAME] - if Filename.find(".H") != -1 or Filename.find(".B") != -1 or \ - Filename.find(".S") != -1: - FHFiles[Index] = [] - except IndexError: - continue -# Now move everything left to BFiles. -BFiles = [] -BFilesBytes = 0 -for File in FHFiles: - if len(File) == 0: - continue - BFilesBytes += File[FH_SIZE] - BFiles.append(File) -BFilesToOff = len(BFiles) -if BFilesToOff == 0: - logIt("All files appear to have been offloaded.") + if Found == False: + DSize = getsize(SDRspec+DFile) + Extras += 1 + logIt(MSGspec, " %d. %s (%s %s)"%(Extras, DFile, \ + fmti(DSize), sP(DSize, ("byte", "bytes"))), False) + if Extras == 0: + logIt(MSGspec, " None.", False) + logIt(MSGspec, "", False) exit(0) -# Create this after we know if there is even going to be anything to offload. -if exists(DirspecSDR) == False: - makedirs(DirspecSDR) - logIt("Saving data files to %s (created)"%DirspecSDR) -else: - logIt("Saving data files to %s (exists)"%DirspecSDR) -logIt("Offloading %s %s, %s %s..."%(fmti(BFilesToOff), sP(BFilesToOff, \ - ("file", "files")), fmti(BFilesBytes), sP(BFilesBytes, ("byte", \ - "bytes")))) -Count = 0 -# Files offloaded. -OffFiles = 0 -# Total bytes offloaded. -OffBytes = 0 -# Bytes from the current file offloaded. -OffFileSize = 0 -for File in BFiles: - OffFileSize = 0 - Offloaded10 = 0 - if len(File) == 0: - continue - Count += 1 - Filename = File[FH_NAME] - BFileSize = File[FH_SIZE] - if BFileSize == -1: - logIt("%d/%d. Getting %s (?)..."%(Count, BFilesToOff, Filename)) - else: - logIt("%d/%d. Getting %s (%s)..."%(Count, BFilesToOff, Filename, \ - fmti(BFileSize))) - try: - urlretrieve("http://%s/%s"%(IPAddr, Filename), "%s%s"%(DirspecSDR, \ - Filename), fileBlockRetrieved) - except Exception as e: - if Offloaded10 > 0: - stdout.write("\n") - logIt("ERROR: Retrieving file %s:"%Filename) - logIt("%s (got ~%s %s)"%(e, fmti(OffFileBytes), sP(OffFileBytes, \ - ("byte", "bytes")))) - logIt("") - exit(1) - if Offloaded10 > 0: - stdout.write("\n") - FileSize = getsize(DirspecSDR+Filename) - if BFileSize != -1: - if BFileSize != FileSize: - logIt("ERROR: Size error. %s: Baler %s, File %s"%(Filename, \ - fmti(BFileSize), fmti(FileSize))) - logIt("") - exit(1) -# Same as above. - if FileSize < 4000: - logIt("STRANGE: File %s is only %s %s."%(Filename, fmti(FileSize), \ - sP(FileSize, ("byte", "bytes")))) - OffFiles += 1 - OffBytes += FileSize -logIt("Offloaded %s %s, %s %s.\a"%(fmti(OffFiles), sP(OffFiles, ("file", \ - "files")), fmti(OffBytes), sP(OffBytes, ("byte", "bytes")))) -logIt("") -exit(0) + +logIt("", "What??\a\n", False) +exit(1) # END: main # END PROGRAM: BLINE diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index c7030b4cfa359432f534ab34d7b314606c3e3c97..3014c78d8d2f4c6f0ef01383f7d2fcc6d75e0eac 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,6 +1,6 @@ package: name: bline - version: 2018.135 + version: 2019.297 source: path: .. diff --git a/setup.cfg b/setup.cfg index 3c6e531150750912196df1fd45072e44980e23e3..d2d5bd00c826c99d8f8ea34a18dca27946a85a5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2019.064 +current_version = 2019.297 commit = True tag = True diff --git a/setup.py b/setup.py index 3752eaa21ccd12c0489c2848889fd7bf4640ab7f..cf074bca4ca7edf1cf8366e0f638ea55510275a0 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,6 @@ with open('README.rst') as readme_file: with open('HISTORY.rst') as history_file: history = history_file.read() - setup( author="IRIS PASSCAL", author_email='software-support@passcal.nmt.edu', @@ -28,7 +27,10 @@ setup( 'bline=bline.bline:main', ], }, - install_requires=[], + install_requires=['psutil', + 'ipaddress', + 'pexpect', + 'subprocess32'], setup_requires = [], extras_require={ 'dev': [ @@ -51,6 +53,6 @@ setup( packages=find_packages(include=['bline']), test_suite='tests', url='https://git.passcal.nmt.edu/passoft/bline', - version='2019.064', + version='2019.297', zip_safe=False, )