diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e579de4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 AllskyTeam + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/allsky_dewheater/allsky_dewheater.py b/allsky_dewheater/allsky_dewheater.py index 1288c63..1080664 100755 --- a/allsky_dewheater/allsky_dewheater.py +++ b/allsky_dewheater/allsky_dewheater.py @@ -9,29 +9,31 @@ - Added extra pin that is triggered with heater pin - Fixed dhtxxdelay (was not implemented) - Fixed max heater time (was not implemented) - +V1.0.2 by Alex Greenland +- Updated code for pi 5 +- Moved to core allsky ''' import allsky_shared as s import time +import sys +import board import adafruit_sht31d import adafruit_dht from adafruit_bme280 import basic as adafruit_bme280 from adafruit_htu21d import HTU21D -import board -import busio -import RPi.GPIO as GPIO from meteocalc import heat_index from meteocalc import dew_point - +from digitalio import DigitalInOut, Direction, Pull + metaData = { "name": "Sky Dew Heater Control", "description": "Controls a dew heater via a temperature and humidity sensor", "module": "allsky_dewheater", - "version": "v1.0.1", + "version": "v1.0.2", "events": [ "periodic" ], - "experimental": "true", + "experimental": "false", "arguments":{ "type": "None", "inputpin": "", @@ -223,8 +225,9 @@ def readSHT31(sht31heater): sensor.heater = sht31heater temperature = sensor.temperature humidity = sensor.relative_humidity - except: - pass + except RuntimeError as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(4, f"ERROR: Module readSHT31 failed on line {eTraceback.tb_lineno} - {e}") return temperature, humidity @@ -238,10 +241,12 @@ def doDHTXXRead(inputpin): try: temperature = dhtDevice.temperature humidity = dhtDevice.humidity - except RuntimeError as error: - s.log(4, "INFO: {}".format(error)) - except Exception as error: - s.log(4, "INFO: {}".format(error)) + except RuntimeError as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(4, f"ERROR: Module doDHTXXRead failed on line {eTraceback.tb_lineno} - {e}") + except Exception as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(4, f"ERROR: Module doDHTXXRead failed on line {eTraceback.tb_lineno} - {e}") return temperature, humidity @@ -276,9 +281,9 @@ def readBme280I2C(i2caddress): if i2caddress != "": try: i2caddressInt = int(i2caddress, 16) - except: - result = "Address {} is not a valid i2c address".format(i2caddress) - s.log(0,"ERROR: {}".format(result)) + except Exception as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(0, f"ERROR: Module readBme280I2C failed on line {eTraceback.tb_lineno} - {e}") try: i2c = board.I2C() @@ -292,8 +297,9 @@ def readBme280I2C(i2caddress): relHumidity = bme280.relative_humidity altitude = bme280.altitude pressure = bme280.pressure - except ValueError: - pass + except ValueError as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(0, f"ERROR: Module readBme280I2C failed on line {eTraceback.tb_lineno} - {e}") return temperature, humidity, pressure, relHumidity, altitude @@ -317,49 +323,50 @@ def readHtu21(i2caddress): temperature = htu21.temperature humidity = htu21.relative_humidity - except ValueError: - pass - + except ValueError as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(4, f"ERROR: Module readHtu21 failed on line {eTraceback.tb_lineno} - {e}") + return temperature, humidity -def setmode(): - try: - GPIO.setmode(GPIO.BOARD) - except: - pass - -def turnHeaterOn(heaterpin, invertrelay): - result = "Turning Heater on" - setmode() - GPIO.setup(heaterpin.id, GPIO.OUT) +def turnHeaterOn(heaterpin, invertrelay, extra=False): + if extra: + type = 'Extra' + else: + type = 'Heater' + + result = f"Turning {type} on using pin {heaterpin}" + pin = DigitalInOut(heaterpin) + pin.switch_to_output() + if invertrelay: - if GPIO.input(heaterpin.id) == 0: - result = "Leaving Heater on" - GPIO.output(heaterpin.id, GPIO.LOW) + pin.value = 0 else: - if GPIO.input(heaterpin.id) == 1: - result = "Leaving Heater on" - GPIO.output(heaterpin.id, GPIO.HIGH) + pin.value = 1 + if not s.dbHasKey("dewheaterontime"): now = int(time.time()) s.dbAdd("dewheaterontime", now) - s.log(1,"INFO: {}".format(result)) + s.log(1,f"INFO: {result}") -def turnHeaterOff(heaterpin, invertrelay): - result = "Turning Heater off" - setmode() - GPIO.setup(heaterpin.id, GPIO.OUT) +def turnHeaterOff(heaterpin, invertrelay, extra=False): + if extra: + type = 'Extra' + else: + type = 'Heater' + + result = f"Turning {type} off using pin {heaterpin}" + pin = DigitalInOut(heaterpin) + pin.direction = Direction.OUTPUT + if invertrelay: - if GPIO.input(heaterpin.id) == 1: - result = "Leaving Heater off" - GPIO.output(heaterpin.id, GPIO.HIGH) + pin.value = 1 else: - if GPIO.input(heaterpin.id) == 0: - result = "Leaving Heater off" - GPIO.output(heaterpin.id, GPIO.LOW) + pin.value = 0 + if s.dbHasKey("dewheaterontime"): s.dbDeleteKey("dewheaterontime") - s.log(1,"INFO: {}".format(result)) + s.log(1,f"INFO: {result}") def getSensorReading(sensorType, inputpin, i2caddress, dhtxxretrycount, dhtxxdelay, sht31heater): temperature = None @@ -409,8 +416,8 @@ def getLastRunTime(): def debugOutput(sensorType, temperature, humidity, dewPoint, heatIndex, pressure, relHumidity, altitude): s.log(1,f"INFO: Sensor {sensorType} read. Temperature {temperature} Humidity {humidity} Relative Humidity {relHumidity} Dew Point {dewPoint} Heat Index {heatIndex} Pressure {pressure} Altitude {altitude}") - -def dewheater(params, event): + +def dewheater(params, event): result = "" sensorType = params["type"] heaterstartupstate = params["heaterstartupstate"] @@ -446,7 +453,7 @@ def dewheater(params, event): shouldRun, diff = s.shouldRun('allskydew', frequency) - if shouldRun: + if shouldRun: try: heaterpin = int(heaterpin) except ValueError: @@ -473,14 +480,14 @@ def dewheater(params, event): s.log(1,"INFO: {}".format(result)) turnHeaterOff(heaterpin, invertrelay) if extrapin != 0: - turnHeaterOff(extrapin, invertextrapin) + turnHeaterOff(extrapin, invertextrapin, True) heater = 'Off' elif force != 0 and temperature <= force: result = "Temperature below forced level {}".format(force) s.log(1,"INFO: {}".format(result)) turnHeaterOn(heaterpin, invertrelay) if extrapin != 0: - turnHeaterOn(extrapin, invertextrapin) + turnHeaterOn(extrapin, invertextrapin, True) heater = 'On' else: if ((temperature-limit) <= dewPoint): @@ -495,9 +502,9 @@ def dewheater(params, event): s.log(1,"INFO: {}".format(result)) turnHeaterOff(heaterpin, invertrelay) if extrapin != 0: - turnHeaterOff(extrapin, invertextrapin) + turnHeaterOff(extrapin, invertextrapin, True) heater = 'Off' - + extraData = {} extraData["AS_DEWCONTROLAMBIENT"] = str(temperature) extraData["AS_DEWCONTROLDEW"] = str(dewPoint) diff --git a/allsky_dewheater/requirements.txt b/allsky_dewheater/requirements.txt index a293a86..e7623bc 100755 --- a/allsky_dewheater/requirements.txt +++ b/allsky_dewheater/requirements.txt @@ -2,5 +2,6 @@ adafruit-circuitpython-sht31d adafruit-circuitpython-bme280 adafruit-circuitpython-dht adafruit-circuitpython-htu21d -Adafruit_DHT +adafruit-circuitpython-dht +barbudor-circuitpython-ina3221 meteocalc \ No newline at end of file diff --git a/allsky_fans/allsky_fans.py b/allsky_fans/allsky_fans.py index a1ca238..f01a23b 100644 --- a/allsky_fans/allsky_fans.py +++ b/allsky_fans/allsky_fans.py @@ -11,7 +11,7 @@ import shutil from vcgencmd import Vcgencmd import board -import RPi.GPIO as GPIO +from digitalio import DigitalInOut, Direction, Pull metaData = { "name": "Control Allsky Fans", @@ -79,40 +79,30 @@ def getTemperature(): return tempC -def setmode(): - try: - GPIO.setmode(GPIO.BOARD) - except: - pass def turnFansOn(fanpin, invertrelay): result = "Turning Fans ON" - setmode() - GPIO.setup(fanpin.id, GPIO.OUT) + pin = DigitalInOut(fanpin) + pin.switch_to_output() + if invertrelay: - if GPIO.input(fanpin.id) == 0: - result = "Leaving Fans ON" - GPIO.output(fanpin.id, GPIO.LOW) + pin.value = 0 else: - if GPIO.input(fanpin.id) == 1: - result = "Leaving Fans ON" - GPIO.output(fanpin.id, GPIO.HIGH) - s.log(1,"INFO: {}".format(result)) + pin.value = 1 + + s.log(1,f"INFO: {result}") def turnFansOff(fanpin, invertrelay): result = "Turning Fans OFF" - setmode() - GPIO.setup(fanpin.id, GPIO.OUT) + pin = DigitalInOut(fanpin) + pin.switch_to_output() if invertrelay: - if GPIO.input(fanpin.id) == 1: - result = "Leaving Fans OFF" - GPIO.output(fanpin.id, GPIO.HIGH) + pin.value = 1 else: - if GPIO.input(fanpin.id) == 0: - result = "Leaving Fans OFF" - GPIO.output(fanpin.id, GPIO.LOW) - s.log(1,"INFO: {}".format(result)) + pin.value = 0 + + s.log(1,f"INFO: {result}") def fans(params, event): result = '' diff --git a/allsky_ina3221/README.md b/allsky_ina3221/README.md new file mode 100644 index 0000000..e050244 --- /dev/null +++ b/allsky_ina3221/README.md @@ -0,0 +1,11 @@ +# AllSky INA3221 Module + +| | | +| ------------ | ------------ | +| **Status** | Experimental | +| **Level** | Experienced | +| **Runs In** | Periodic | + +A simple module to read 1 to 3 channels from an INA3221 voltage and current sensor. + +These modules can be useful for monitoring the current being fed to a dew heater to determine if its actually working or not diff --git a/allsky_ina3221/allsky_ina3221.py b/allsky_ina3221/allsky_ina3221.py new file mode 100644 index 0000000..3b33fc5 --- /dev/null +++ b/allsky_ina3221/allsky_ina3221.py @@ -0,0 +1,161 @@ +import sys +import board +import allsky_shared as s +from barbudor_ina3221.full import * + +metaData = { + "name": "Current/voltage monitoring", + "description": "Monitors current and voltage using an ina3221", + "module": "allsky_ina3221", + "version": "v1.0.1", + "events": [ + "periodic" + ], + "experimental": "true", + "arguments":{ + "i2caddress": "", + "c1enable": "false", + "c1name": "", + "c2enable": "false", + "c2name": "", + "c3enable": "false", + "c3name": "", + "extradatafilename": "allskyina3221.json" + }, + "argumentdetails": { + "i2caddress": { + "required": "false", + "description": "I2C Address", + "help": "Override the standard i2c address for a device. NOTE: This value must be hex i.e. 0x40" + }, + "c1enable" : { + "required": "false", + "description": "Enable Channel 1", + "help": "Enable channel 1 on the sensor", + "type": { + "fieldtype": "checkbox" + } + }, + "c1name" : { + "required": "false", + "description": "Channel 1 name", + "help": "Name of the channel 1 allsky overlay variable" + }, + "c2enable" : { + "required": "false", + "description": "Enable Channel 2", + "help": "Enable channel 2 on the sensor", + "type": { + "fieldtype": "checkbox" + } + }, + "c2name" : { + "required": "false", + "description": "Channel 2 name", + "help": "Name of the channel 2 allsky overlay variable" + }, + "c3enable" : { + "required": "false", + "description": "Enable Channel 3", + "help": "Enable channel 3 on the sensor", + "type": { + "fieldtype": "checkbox" + } + }, + "c3name" : { + "required": "false", + "description": "Channel 3 name", + "help": "Name of the channel 3 allsky overlay variable" + }, + "extradatafilename": { + "required": "true", + "description": "Extra Data Filename", + "tab": "Extra Data", + "help": "The name of the file to create with the voltage/current data for the overlay manager" + } + } +} + + +def debugOutput(sensorType, temperature, humidity, dewPoint, heatIndex, pressure, relHumidity, altitude): + s.log(1,f"INFO: Sensor {sensorType} read. Temperature {temperature} Humidity {humidity} Relative Humidity {relHumidity} Dew Point {dewPoint} Heat Index {heatIndex} Pressure {pressure} Altitude {altitude}") + +def readChannel(ina3221, channel): + ina3221.enable_channel(channel) + busVoltage = ina3221.bus_voltage(channel) + shuntVoltage = ina3221.shunt_voltage(channel) + current = ina3221.current(channel) + voltage = round(busVoltage + shuntVoltage,2) + current = round(abs(current),3) + + s.log(4, f"INFO: Channel {channel} read, voltage {voltage}, current {current}. Bus Voltage {busVoltage}, Shunt Voltage {shuntVoltage}") + + return voltage, current + +def ina3221(params, event): + result = "Ina3221 read ok" + + try: + c1enabled = params["c1enable"] + c1name = params["c1name"].upper() + c2enabled = params["c2enable"] + c2name = params["c2name"].upper() + c3enabled = params["c3enable"] + c3name = params["c3name"].upper() + extradatafilename = params['extradatafilename'] + + i2cBus = board.I2C() + ina3221 = INA3221(i2cBus) + + if INA3221.IS_FULL_API: + ina3221.update(reg=C_REG_CONFIG, + mask=C_AVERAGING_MASK | + C_VBUS_CONV_TIME_MASK | + C_SHUNT_CONV_TIME_MASK | + C_MODE_MASK, + value=C_AVERAGING_128_SAMPLES | + C_VBUS_CONV_TIME_8MS | + C_SHUNT_CONV_TIME_8MS | + C_MODE_SHUNT_AND_BUS_CONTINOUS) + + extraData = {} + extraData[f"AS_{c1name}VOLTAGE"] = "N/A" + extraData[f"AS_{c1name}CURRENT"] = "N/A" + extraData[f"AS_{c2name}VOLTAGE"] = "N/A" + extraData[f"AS_{c2name}CURRENT"] = "N/A" + extraData[f"AS_{c3name}VOLTAGE"] = "N/A" + extraData[f"AS_{c3name}CURRENT"] = "N/A" + + if c1enabled: + voltage, current = readChannel(ina3221,1) + extraData[f"AS_{c1name}VOLTAGE"] = str(voltage) + extraData[f"AS_{c1name}CURRENT"] = str(current) + + if c2enabled: + voltage, current = readChannel(ina3221,2) + extraData[f"AS_{c2name}VOLTAGE"] = str(voltage) + extraData[f"AS_{c2name}CURRENT"] = str(current) + + if c3enabled: + voltage, current = readChannel(ina3221,3) + extraData[f"AS_{c3name}VOLTAGE"] = str(voltage) + extraData[f"AS_{c3name}CURRENT"] = str(current) + + s.saveExtraData(extradatafilename,extraData) + except Exception as e: + eType, eObject, eTraceback = sys.exc_info() + s.log(0, f'ERROR: ina3221 failed on line {eTraceback.tb_lineno} - {e}') + + return result + +def ina3221_cleanup(): + moduleData = { + "metaData": metaData, + "cleanup": { + "files": { + "allskyina3221.json" + }, + "env": {} + } + } + s.cleanupModule(moduleData) diff --git a/allsky_ina3221/requirements,txt b/allsky_ina3221/requirements,txt new file mode 100644 index 0000000..a4b7288 --- /dev/null +++ b/allsky_ina3221/requirements,txt @@ -0,0 +1 @@ +barbudor-circuitpython-ina3221 \ No newline at end of file diff --git a/allsky_influxdb/allsky_influxdb.py b/allsky_influxdb/allsky_influxdb.py index 237d019..4117162 100755 --- a/allsky_influxdb/allsky_influxdb.py +++ b/allsky_influxdb/allsky_influxdb.py @@ -143,7 +143,7 @@ def createJSONData(values): if var.startswith("AS_") or var.startswith("ALLSKY_"): if var in vars: try: - fields[var] = s.float(s.getEnvironmentVariable(var)) + fields[var] = s.asfloat(s.getEnvironmentVariable(var)) except: pass diff --git a/allsky_lightgraph/requirements.txt b/allsky_lightgraph/requirements.txt index 6aaad6e..c6e1d46 100644 --- a/allsky_lightgraph/requirements.txt +++ b/allsky_lightgraph/requirements.txt @@ -1,2 +1 @@ pyephem -opencv_python==4.5.3.56 diff --git a/allsky_rain/allsky_rain.py b/allsky_rain/allsky_rain.py index 85a39b6..0252fee 100755 --- a/allsky_rain/allsky_rain.py +++ b/allsky_rain/allsky_rain.py @@ -11,7 +11,7 @@ ''' import allsky_shared as s import os -import RPi.GPIO as GPIO +from digitalio import DigitalInOut, Direction, Pull metaData = { "name": "Rain detection", @@ -65,10 +65,10 @@ def rain(params, event): if inputpin != 0: try: - GPIO.setmode(GPIO.BCM) - GPIO.setup(inputpin, GPIO.IN) - - pinState = GPIO.input(inputpin) + rainpin = s.getGPIOPin(inputpin) + pin = DigitalInOut(rainpin) + + pinState = pin.value resultState = "Not Raining" raining = "" diff --git a/allsky_sqm/allsky_sqm.py b/allsky_sqm/allsky_sqm.py index 8d0379a..0f38322 100755 --- a/allsky_sqm/allsky_sqm.py +++ b/allsky_sqm/allsky_sqm.py @@ -10,8 +10,6 @@ v1.0.1 by Damian Grocholski (Mr-Groch) - Use of weightedSqmAvg inspired by indi-allsky (https://github.com/aaronwmorris/indi-allsky) - Added example default formula -v1.0.2 by Damian Grocholski (Mr-Groch) -- Added useclearsky and rain flag ''' import allsky_shared as s @@ -23,7 +21,7 @@ "name": "Sky Quality", "description": "Calculates sky quality", "module": "allsky_sqm", - "version": "v1.0.2", + "version": "v1.0.1", "events": [ "night" ], @@ -34,8 +32,7 @@ "debug": "false", "debugimage": "", "roifallback": 5, - "formula": "21.53 + (-0.03817 * weightedSqmAvg)", - "useclearsky": "false" + "formula": "21.53 + (-0.03817 * weightedSqmAvg)" }, "argumentdetails": { "mask" : { @@ -70,14 +67,6 @@ "description": "Adjustment Forumla", "help": "Formula to adjust the read mean value, default can be a good starting point. This forumla can use only Pythons inbuilt maths functions and basic mathematical operators. Please see the documentation for more details of the formula variables available" }, - "useclearsky" : { - "required": "false", - "description": "Use Clear Sky", - "help": "If available use the results of the clear sky module. If the sky is not clear SQM calculation will be skipped", - "type": { - "fieldtype": "checkbox" - } - }, "debug" : { "required": "false", "description": "Enable debug mode", @@ -102,8 +91,8 @@ def addInternals(ALLOWED_NAMES): for internal in internals: val = s.getEnvironmentVariable(internal) key = internal.replace('AS_', '') - ALLOWED_NAMES[key] = s.float(val) - + ALLOWED_NAMES[key] = s.asfloat(val) + return ALLOWED_NAMES def evaluate(expression, sqmAvg, weightedSqmAvg): @@ -131,105 +120,88 @@ def sqm(params, event): debugimage = params["debugimage"] fallback = int(params["roifallback"]) - raining, rainFlag = s.raining() - skyState, skyClear = s.skyClear() - - useclearsky = params["useclearsky"] - if not useclearsky: - skyClear = True - - if not rainFlag: - if skyClear: - if debugimage != "": - image = cv2.imread(debugimage) - if image is None: - image = s.image - s.log(0, "WARNING: Debug image set to {0} but cannot be found, using latest allsky image".format(debugimage)) - else: - s.log(0, "WARNING: Using debug image {0}".format(debugimage)) - else: - image = s.image + if debugimage != "": + image = cv2.imread(debugimage) + if image is None: + image = s.image + s.log(0, "WARNING: Debug image set to {0} but cannot be found, using latest allsky image".format(debugimage)) + else: + s.log(0, "WARNING: Using debug image {0}".format(debugimage)) + else: + image = s.image - imageMask = None - if mask != "": - maskPath = os.path.join(s.getEnvironmentVariable("ALLSKY_OVERLAY"),"images",mask) - imageMask = cv2.imread(maskPath,cv2.IMREAD_GRAYSCALE) - if debug: - s.writeDebugImage(metaData["module"], "image-mask.png", imageMask) + imageMask = None + if mask != "": + maskPath = os.path.join(s.getEnvironmentVariable("ALLSKY_OVERLAY"),"images",mask) + imageMask = cv2.imread(maskPath,cv2.IMREAD_GRAYSCALE) + if debug: + s.writeDebugImage(metaData["module"], "image-mask.png", imageMask) - if len(image.shape) == 2: - grayImage = image - else: - grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - - if imageMask is not None: - if grayImage.shape == imageMask.shape: - grayImage = cv2.bitwise_and(src1=grayImage, src2=imageMask) - if debug: - s.writeDebugImage(metaData["module"], "masked-image.png", grayImage) - else: - s.log(0,"ERROR: Source image and mask dimensions do not match") - - imageHeight, imageWidth = grayImage.shape[:2] - try: - roiList = roi.split(",") - x1 = int(roiList[0]) - y1 = int(roiList[1]) - x2 = int(roiList[2]) - y2 = int(roiList[3]) - except: - if len(roi) > 0: - s.log(0, "ERROR: SQM ROI is invalid, falling back to {0}% of image".format(fallback)) - else: - s.log(1, "INFO: SQM ROI not set, falling back to {0}% of image".format(fallback)) - fallbackAdj = (100 / fallback) - x1 = int((imageWidth / 2) - (imageWidth / fallbackAdj)) - y1 = int((imageHeight / 2) - (imageHeight / fallbackAdj)) - x2 = int((imageWidth / 2) + (imageWidth / fallbackAdj)) - y2 = int((imageHeight / 2) + (imageHeight / fallbackAdj)) - - croppedImage = grayImage[y1:y2, x1:x2] + if len(image.shape) == 2: + grayImage = image + else: + grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + if imageMask is not None: + if grayImage.shape == imageMask.shape: + grayImage = cv2.bitwise_and(src1=grayImage, src2=imageMask) if debug: - s.writeDebugImage(metaData["module"], "cropped-image.png", croppedImage) - - maxExposure_s = s.float(s.getSetting("nightmaxautoexposure")) / 1000 - exposure_s = s.float(s.getEnvironmentVariable("AS_EXPOSURE_US")) / 1000 / 1000 - maxGain = s.float(s.getSetting("nightmaxautogain")) - gain = s.float(s.getEnvironmentVariable("AS_GAIN")) - - sqmAvg = cv2.mean(src=croppedImage)[0] - weightedSqmAvg = (((maxExposure_s - exposure_s) / 10) + 1) * (sqmAvg * (((maxGain - gain) / 10) + 1)) - - result = "Final SQM Mean calculated as {0}, weighted {1}".format(sqmAvg, weightedSqmAvg) - if formula != '': - s.log(1,"INFO: SQM Mean calculated as {0}, weighted {1}".format(sqmAvg, weightedSqmAvg)) - try: - sqm = float(evaluate(formula, sqmAvg, weightedSqmAvg)) - result = "Final SQM calculated as {0}".format(sqm) - s.log(1,"INFO: Ran Formula: " + formula) - s.log(1,"INFO: " + result) - except Exception as e: - result = "Error " + str(e) - sqm = weightedSqmAvg - s.log(0, "ERROR: " + result) + s.writeDebugImage(metaData["module"], "masked-image.png", grayImage) else: - sqm = weightedSqmAvg - s.log(1,"INFO: " + result) - - os.environ["AS_SQM"] = str(sqm) + s.log(0,"ERROR: Source image and mask dimensions do not match") + + imageHeight, imageWidth = grayImage.shape[:2] + try: + roiList = roi.split(",") + x1 = int(roiList[0]) + y1 = int(roiList[1]) + x2 = int(roiList[2]) + y2 = int(roiList[3]) + except: + if len(roi) > 0: + s.log(0, "ERROR: SQM ROI is invalid, falling back to {0}% of image".format(fallback)) else: - result = "Sky is not clear so ignoring SQM calculation" - s.log(4,"INFO: {0}".format(result)) - os.environ["AS_SQM"] = "Disabled" + s.log(1, "INFO: SQM ROI not set, falling back to {0}% of image".format(fallback)) + fallbackAdj = (100 / fallback) + x1 = int((imageWidth / 2) - (imageWidth / fallbackAdj)) + y1 = int((imageHeight / 2) - (imageHeight / fallbackAdj)) + x2 = int((imageWidth / 2) + (imageWidth / fallbackAdj)) + y2 = int((imageHeight / 2) + (imageHeight / fallbackAdj)) + + croppedImage = grayImage[y1:y2, x1:x2] + + if debug: + s.writeDebugImage(metaData["module"], "cropped-image.png", croppedImage) + + maxExposure_s = s.asfloat(s.getSetting("nightmaxautoexposure")) / 1000 + exposure_s = s.asfloat(s.getEnvironmentVariable("AS_EXPOSURE_US")) / 1000 / 1000 + maxGain = s.asfloat(s.getSetting("nightmaxautogain")) + gain = s.asfloat(s.getEnvironmentVariable("AS_GAIN")) + + sqmAvg = cv2.mean(src=croppedImage)[0] + weightedSqmAvg = (((maxExposure_s - exposure_s) / 10) + 1) * (sqmAvg * (((maxGain - gain) / 10) + 1)) + + result = "Final SQM Mean calculated as {0}, weighted {1}".format(sqmAvg, weightedSqmAvg) + if formula != '': + s.log(1,"INFO: SQM Mean calculated as {0}, weighted {1}".format(sqmAvg, weightedSqmAvg)) + try: + sqm = float(evaluate(formula, sqmAvg, weightedSqmAvg)) + result = "Final SQM calculated as {0}".format(sqm) + s.log(1,"INFO: Ran Formula: " + formula) + s.log(1,"INFO: " + result) + except Exception as e: + result = "Error " + str(e) + sqm = weightedSqmAvg + s.log(0, "ERROR: " + result) else: - result = "Its raining so ignorning SQM calculation" - s.log(4,"INFO: {0}".format(result)) - os.environ["AS_SQM"] = "Disabled" + sqm = weightedSqmAvg + s.log(1,"INFO: " + result) + + os.environ["AS_SQM"] = str(sqm) return result -def sqm_cleanup(): +def rain_cleanup(): moduleData = { "metaData": metaData, "cleanup": { diff --git a/install.sh b/install.sh index 5340282..415f595 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,35 @@ #!/bin/bash +[[ -z "${ALLSKY_HOME}" ]] && export ALLSKY_HOME="$( realpath "$( dirname "${BASH_ARGV0}" )/.." )" + +#shellcheck source-path=. +source "${ALLSKY_HOME}/variables.sh" || exit "${ALLSKY_ERROR_STOP}" +#shellcheck source-path=scripts +source "${ALLSKY_SCRIPTS}/functions.sh" || exit "${ALLSKY_ERROR_STOP}" + +trap "exit 0" SIGTERM SIGINT + echo "Validating and launching installer" echo sudo apt install -y pip + +# +# If a using bookworm or later then use a venv +# +if [[ ${PI_OS} != "buster" ]] && [[ ${PI_OS} != "bullseye" ]] ; then + source "${ALLSKY_HOME}/venv/bin/activate" + echo "INFO - Using Python venv" +fi + pip3 install packaging pip3 install whiptail-dialogs -python3 module-installer.py \ No newline at end of file +python3 module-installer.py + +# +# Deactivate the Python Virtual Environment if we used it +# +if [[ ${PI_OS} != "buster" ]] && [[ ${PI_OS} != "bullseye" ]] ; then + deactivate +fi \ No newline at end of file