Module boltwood

Introduction

A Boltwood CS III Serial API wrapper version 0.1 for Python 3.10

Copyright 2024 Diffraction Limited

Requirements

  • Python 3.10 or above
  • PySerial 3.5 or above

Getting Started

Installing dependencies

NOTE: This guide assumes you have Python 3.10 or greater installed on your system with the installation added to your PATH environment variable, and are familiar with basic Python toolchain tools like PyPI. You can verify this by executing:

>> python --version
Python 3.11.8 (tags/v3.11.8:db85d51, Feb  6 2024, 22:03:32) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

Begin by downloading the latest Boltwood Python package from https://cdn.diffractionlimited.com/down/pyboltwood.zip

In this package you'll find:

  • README.md: this document
  • boltwood.py: the core Boltwood CSIII Python module
  • requirements.txt: a list of package requirements generated by Diffraction Limited using pip freeze

Once you've downloaded and extracted the contents of the Python package to a directory on your machine, open a command line and navigate to that folder. From that folder, you can install the package's dependencies by executing:

>> pip install -r requirements.txt

Determining the Boltwood's serial port

In order to communicate with your Boltwood, you need to discover which port the device is connected to. Unfortunately, every operating system has a different way of presenting serial port access, so you'll need to familiarize yourself with the process for your environment.

Windows

Windows list connected serial devices in the Device Manager under "Ports (COM & LPT)". Boltwood devices list as "USB Serial Port (COM#)", where "COM#" will be something like "COM3". That value is the port you'll be addressing when you create the Boltwood class instance.

Ubuntu 22.04 LTS

Linux drivers list their serial devices in the /dev folder. Our serial converters will typically list as /dev/ttyUSB# where "ttyUSB#" will be something like "ttyUSB0". This file needs to be given read/write privileges. This can be achieved with the following command:

$ sudo usermod -a -G dialout $USER

where you replace $USER with your user account name.

The serial API

The boltwood package attempts to reflect the Boltwood serial API as closely as possible. The API is designed to operate similarly to a thinned down HTTP protocol where requests have a verb (e.g. "G" for Get) followed by an interface endpoint (e.g. "DD" for Device Descriptor) followed by a property (e.g. "serial" for Serial Number) and optionally a value. Responses will always have a status code and optionally a return value or error message in the event the message is a failure.

Messages are case insensitive and must be terminated by a linefeed character (\n).

Requests

Requests have the following structure:

verb interface parameter [value]\n

Where verb can be:

  • G for "Get", used for retrieving readable properties on a supplied interface
  • P for "Put", used for updating writable properties on a supplied interface

interface can be:

  • OC for "Observing Conditions", reflecting the ASCOM Observing Conditions interface
  • SM for "Safety Monitor", reflecting the ASCOM Safety Monitor interface
  • DD for "Device Descriptor", used for identifying information and user device settings
  • EN for "Engineering Data", used to access raw sampling and condition data

parameter is interface dependent. Properties for the various interfaces are documented in the ObservingConditions, SafetyMonitor, DeviceDescriptor, and EngineeringData classes below.

value is an optional parameter (required for Put requests) that supplies the value to pass to the Boltwood.

Responses

Responses have the following structure:

status_code [value|error_message]\n

Where status_code can be:

  • 0 for Success
  • 1 for Client Error (the command supplied was invalid)
  • 2 for Server Error (the device was unable to comply with the command)

value is provided on status_code success for Get requests, and is the value obtained from the Boltwood.

error_message is a human-readable error message returned when status_code is non-zero describing the encountered error in detail.

Examples

Retrieving a serial number

>> G DD SERIAL
0 BCS3S24010203\n

Setting up your wireless connection

>> P DD STA_SSID MyObservatorySSID\n
0

>> P DD STA_PASS MySuperSecurePassword\n
0

Retrieving all Engineering Data properties at once

>> G EN ALL\n
0 20 0 ...

Attempting to set a read-only property (worth a shot)

>> P OC TEMPERATURE 21\n
1 Invalid Argument: property 'temperature' is read-only.

Python wrappers

The Python wrapper Boltwood class acts as a controller that executes your verbs: "Get" via Boltwood.get() and "Put" via Boltwood.put(). You can then access the various interfaces and properties via access keys supplied as parameters to those functions. Interface access keys are defined as class variables in the Interfaces class, while Property access keys are defined as class variables in the ObseringConditions, SafetyMonitor, DeviceDescriptor, and EngineeringData classes.

These wrapper functions return a tuple where the first entry is True or False depending on whether the command succeeded, and the second entry is either the value retrieved (if a value was requested/retrieved) or an error message (if the command failed).

Examples

Retrieving a serial number

>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.get(Interfaces.DD, DeviceDescriptor.SERIAL)
(True, "BCS3S24010203") # Success

Setting up your wireless connection

>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyObservatorySSID")
(True, "") # Success
>>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_PASS, "MySuperSecurePassword")
(True, "") # Success

Attempting to set a read-only property (a man can dream…)

>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE, 21)
(False, "Invalid argument: property 'temperature' is read-only.") # Error: Invalid argument

Convenience Accessors

We've also wrapped the interface accessors into their own functions:

Where you can pass an interface access key as a parameter to the function to retrieve/set properties depending on their permissions.

# Interface accessor wrappers
>>> from boltwood import Boltwood, EngineeringData

>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.getDD(DeviceDescriptor.SERIAL)
(True, 'BCS3S24010203') # Success

Amalgamation Accessors

We've provided convenience accessors for interface amalgamators that return the entire interface property listings:

These functions can be used in conjunction with their respective access key wrapper classes to parse the output safely:

# Parsing Engineering Data amalgamator
>>> rc, data = bcs.getENAll()
>>> if rc == True: 
>>>    print(f'Failed to retrieve Engineering Data: {data}')
>>>    exit(1)
>>> ed = EngineeringData(data)
>>> ed[EngineeringData.AMBIENT_TEMP]
"-10"

# Parsing the Observing Conditions amalgamator
>>> rc, data = bcs.getOCAll()
>>> if rc == True: 
>>>     print(f'Failed to retrieve Observing Conditions: {data}')
>>>     exit(1)

>>> oc = ObservingConditions(data)
"-10"

Your first program

Once you've installed boltwood.py's dependencies and determined the port your Boltwood is connected to, you can write your first application. Create a new directory and place "boltwood.py" in it. Then create a new file, named "example.py" and paste the following code into it:

# example.py
from boltwood import Boltwood, DeviceDescriptor

# Sets up a Boltwood instance on Windows COM port `"COM1"` 
# and open the connection. Always wrap your code in a try-except
# block to prevent abnormal program termination
try:
    bcs = Boltwood("COM1")
    bcs.open()

    # Attempt to retrieve Device Descriptor's Serial property, handle any errors
    rc, value = bcs.get(Interface.DD, DeviceDescriptor.SERIAL)
    if not rc:
        # Print the supplied error message to screen
        print(f'There was an error retrieving your device\'s serial number: {value}')
        exit(1)

    # Print the device's returned serial number
    print(f'Success! {value}')
except:
    print("Failed to connect to Boltwood CSIII")

Be sure to update "COM1" with whatever serial port identifier your environment requires to communicate with your device.

Accessing Thresholds & Safety Triggers

Thresholds and Safety Triggers are programmable via the serial API. These values represent transitions between two conditions (e.g. Thresholds.CLEAR_CLOUDY is the value of Sky-Ambient where the cloud sensor transitions from "Clear" to "Cloudy"), and whether they are used to determine the safe/unsafe condition and trigger the roof close signal. You can access them via the Observing Conditions interface, and we've provided a reflection class that parses the response string from the Boltwood CSIII into a dictionary of threshold and safety trigger values. You can use the Thresholds.threshValue class variable to look-up what value will trigger a given condition (e.g. Thresholds.CLEAR_CLOUDY) and use Thresholds.roofTrig to check whether that condition is actively used as a safety trigger.

Retrieving device thresholds/safety triggers:

# Load Boltwood library and connect to device
>>> from boltwood import Boltwood, Thresholds
>>> bcs = Boltwood("COM1")
>>> bcs.open()

# Obtain thresholds from the device and parse them using our Thresholds reflection class 
>>> rc, raw_values = bcs.getOCThresholds()
>>> values = Thresholds(raw_values)

# Access the parsed values by transition access key
>>> values.threshValue[Thresholds.CLEAR_CLOUDY] # Access threshold's value
-10
>>> values.roofTrig[Thresholds.CLEAR_CLOUDY] # Access whether transition is active as safety trigger
0
>>> values[Thresholds.CLEAR_CLOUDY] # Access tuple of threshold value & safety trigger
(-10, 0)

Updating a threshold/safety trigger:

# Load Boltwood library and connect to device
>>> from boltwood import Boltwood, Thresholds
>>> bcs = Boltwood("COM1")
>>> bcs.open()

# Obtain thresholds from the device and parse them using our Thresholds reflection class 
>>> rc, raw_values = bcs.getOCThresholds()
>>> values = Thresholds(raw_values)

# Update the desired threshold from -10 degrees Celcius to -15, and set it as an active safety trigger
>>> values.threshValue[Thresholds.CLEAR_CLOUDY] = -15
>>> values.roofTrig[Thresholds.CLEAR_CLOUDY] = 1

# Submit changes to Boltwood
>>> bcs.putOCThresholds(values.to_string())
(True, "") # Success

License

This software is licensed under the MIT License.

Copyright 2024 Diffraction Limited

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.
Expand source code
"""

.. include:: ./README.md

"""

# Module: boltwood.py
# Author: Adam Robichaud <arobichaud@diffractionlimited.com>
# License: MIT License
#
# Copyright 2024 Diffraction Limited
#
# 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.

import serial
import serial.tools.list_ports as serial_ports


class Interfaces:
    """
    A list of access keys used when accessing data via the `Boltwood.get()` and `Boltwood.put()` methods
    
    See Also
    --------
    Boltwood, ObservingConditions, SafetyMonitor, DeviceDescriptor, EngineeringData

    Examples
    --------
    >>> from boltwood import Boltwood, Interfaces, ObservingConditions, DeviceDescriptor
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
    (True, "-10")

    >>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
    (True, "")

    >>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE)
    (False, "Invalid Argument: property 'temperature' is read-only")
    """
    
    OC = 'oc'
    """Access key for Observing Conditions serial interface"""

    SM = 'sm'
    """Access key for Safety Monitor serial interface"""

    DD = 'dd'
    """Access key for Device Descriptors serial interface"""

    EN = 'en'
    """Access key for Engineering Data serial interface"""

class ObservingConditions:
    ALL = 'all'
    """Access key for a space-delimited list of all parameters in ObservingCondition (read-only)"""

    AVERAGE_PERIOD = 'averageperiod' 
    """Access key for the time period over which observations will be averaged in hours (read-only)"""
    
    CLOUD_COVER = 'cloudcover' 
    """Access key for an estimate of the sky's cloud coverage in % (read-only)"""
    
    DEWPOINT = 'dewpoint' 
    """Access key for the atmospheric dew point at the observatory in degrees Celcius (read-only)"""
    
    HUMIDITY = 'humidity' 
    """Access key for the relative humidity at the observatory in % (read-only)"""
    
    PRESSURE = 'pressure' 
    """Access key for the atmospheric pressure at the observatory in hPa (read-only)"""
    
    RAIN_RATE = 'rainrate' 
    """Access key for an estimate of the rain rate at the observatory in mm/hr (read-only)"""
    
    SKY_BRIGHTNESS = 'skybrightness' 
    """Access key for an estimate of the sky brightness at the observatory in Lux (read-only)"""
    
    SKY_QUALITY = 'skyquality' 
    """Access key for the ASCOM sky quality property. Not supported, reports as 'NA' (read-only)"""
    
    SKY_TEMPERATURE = 'skytemperature' 
    """Access key for the sky temperature at the observatory in degrees Celcius (read-only)"""
    
    STAR_FWHM = 'starfwhm' 
    """Access key for the ASCOM star FWHM property. Not supported, reports as 'NA' (read-only)"""
    
    TEMPERATURE = 'temperature' 
    """Access key for the ambient temperature at the observatory in degrees Celcius (read-only)"""
    
    WIND_DIRECTION = 'winddireciton' 
    """Access key for the ASCOM Wind Direction property. Not supported, reports as 'NA' (read-only)"""
    
    WIND_GUST = 'windgust' 
    """Access key for the ASCOM Wind Gust property. Not supported, reports as 'NA' (read-only)"""
    
    WIND_SPEED = 'windspeed' 
    """Returns the wind speed at the observatory in m/s (read-only)"""

    keys_all = [
        AVERAGE_PERIOD,
        CLOUD_COVER,
        DEWPOINT,
        HUMIDITY,
        PRESSURE,
        RAIN_RATE,
        SKY_BRIGHTNESS,
        SKY_QUALITY,
        SKY_TEMPERATURE,
        STAR_FWHM,
        TEMPERATURE,
        WIND_DIRECTION,
        WIND_GUST,
        WIND_SPEED,
    ]
    """List of all keys provided by the 'all' access key, in proper API order"""

    _values = {}

    def __init__(self, str):
        """
        ObservingConditions 'all' parser and list of ObservingConditions access keys

        Parameters
        ----------
        str : string
            Results of a call to `Boltwood.getOC('all')`

        Examples
        --------
        >>> from boltwood import Boltwood, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCAll()
        >>> values = ObservingConditions(raw_values)
        >>> print(f'{values[ObservingConditions.WIND_SPEED]}')
        1.0
        """
        self._parse(str)

    def _parse(self, str):
        arr = str.split(' ')
        for i in range(0, len(self.keys_all)):
            self._values[self.keys_all[i]] = arr[i]

    def __getitem__(self, key):
        """
        Array accessor for parsed values, indexed by access keys listed above

        Parameters
        ----------
        key : string
            ObservingConditions access key to retrieve

        Returns
        -------
        value : string
            Stored value for provided access key 
        """
        return self._values[key]

class SafetyMonitor:
    """
    A list of access keys used when accessing data via `Boltwood.getSM()`

    Examples
    --------

    >>> from boltwood import Boltwood, SafetyMonitor
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> bcs.getSM(SafetyMonitor.IS_SAFE)
    (True, "1")
    """

    IS_SAFE = "isSafe"
    """
    Access key for the ASCOM Safety Monitor 'isSafe' property (read-only)
    
    1 is safe, 0 is unsafe
    """

class DeviceDescriptor:
    """
    A list of access keys when accessing data via `Boltwood.getDD()` or `Boltwood.putDD()`
    """

    SERIAL = 'serial'
    """Access key for the device's unique serial number (read-only)"""

    FW_REVISION = "fwrev"
    """Access key for the device's firmware revision number (read-only)"""

    STA_IP = "sta_ip"
    """Access key for the device's WiFi Station DHCP-assigned IP address (read-only)"""

    STA_SSID = "sta_ssid"
    """Access key for the device's WiFi Station SSID (network name)"""

    STA_PASS = "sta_pass"
    """Access key for the device's WiFi Station passphrase (write-only)"""

    AP_SSID = "ap_ssid"
    """Access key for the device's factory-assigned WiFi Access Point SSID (read-only)"""

class EngineeringData:
    WIND_COLD       = 'windColdSensor' 
    """Access key for EngineeringData Windspeed sensor cold stock temperature measurement (read-only)"""

    WIND_HOT        = 'windHotSensor'  
    """Access key for EngineeringData Windspeed sensor hot stock temperature measurement (read-only)"""

    WINDSPEED       = 'windSpeed'      
    """Access key for EngineeringData Windspeed sensor measurement (read-only)"""

    PRESSURE        = 'pressure'       
    """Access key for EngineeringData Pressure sensor measurement (read-only)"""

    PRESSURE_TEMP   = 'pressureTemp'   
    """Access key for EngineeringData Pressure sensor temperature measurement (read-only)"""

    AMBIENT_TEMP    = 'ambientTemp'
    """Access key for EngineeringData Ambient Temperature sensor temperature measurement (read-only)"""

    VOLTAGE         = 'voltage'        
    """Access key for EngineeringData Power Supply Voltage sensor measurement (read-only)"""

    DAYLIGHT        = 'daylightSensor' 
    """Access key for EngineeringData Daylight sensor measurement (read-only)"""

    SKY_AMBIENT     = 'skyAmbientTemp' 
    """Access key for EngineeringData Sky Temperature minus Ambient Temperature measurement (read-only)"""

    SKY_TEMP        = 'skyTemp'        
    """Access key for EngineeringData Sky Temperature sensor measurement (read-only)"""

    CASE_TEMP       = 'caseTemp'       
    """Access key for EngineeringData Case Temperature sensor measurement (read-only)"""

    RAIN_RAW        = 'rainRaw'        
    """Access key for EngineeringData Rain sensor's instantaneous raw drop sample (read-only)"""

    RAIN_DPM        = 'rainDpm'        
    """Access key for EngineeringData Rain sensor drops/minute measurement (read-only)"""

    HUMIDITY        = 'humidity'       
    """Access key for EngineeringData Humidity sensor measurement (read-only)"""

    HUMIDITY_TEMP   = 'humidityTemp'   
    """Access key for EngineeringData Humidity sensor onboard temperature measurement (read-only)"""

    DEWPOINT        = 'dewpoint'      
    """Access key for EngineeringData Dewpoint sensor calculated value (read-only)"""

    COND_OVERCAST   = 'condOvercast'
    """Access key for EngineeringData Overcast condition state (read-only)"""

    COND_WIND       = 'condWind'
    """Access key for EngineeringData Windy condition state (read-only)"""

    COND_BRIGHTNESS = 'condBrightness'
    """Access key for EngineeringData Brightness condition state (read-only)"""

    COND_PRECIP     = 'condPrecipitation'
    """Access key for EngineeringData Precipitation condition state (read-only)"""

    COND_WEATHER    = 'condWeather'
    """Access key for EngineeringData Overall Weather condition state (read-only)"""

    COND_HUMIDITY   = 'condHumidity'
    """Access key for EngineeringData Humidity condition state (read-only)"""

    COND_PRESSURE   = 'condPressure'
    """Access key for EngineeringData Pressure condition state (read-only)"""

    COND_VOLTAGE    = 'condVoltage'
    """Access key for EngineeringData Voltage condition state (read-only)"""

    def _format_lmh(self, val):
        """
        Convenience function for formatting low/medium/high condition values
        """
        if val == '0': return "low"
        if val == '1': return "med"
        if val == '2': return "high"
        return "???"

    def _format_overcast(self):
        """
        Convenience function for formatting overcast condition values
        """
        val = self.values[self.COND_OVERCAST]
        if val == '0': return 'clear'
        if val == '1': return 'cloudy'
        if val == '2': return 'vcloudy'
        return '???'

    def _format_wind(self):
        """
        Convenience function for formatting windy condition values
        """
        val = self.values[self.COND_WIND]
        if val == '0': return 'calm'
        if val == '1': return 'windy'
        if val == '2': return 'vwindy'
        return '???'

    def _format_bright(self):
        """
        Convenience function for formatting brightness condition values
        """
        val = self.values[self.COND_BRIGHTNESS]
        if val == '0': return 'dark'
        if val == '1': return 'light'
        if val == '2': return 'vlight'
        return '???'

    def _format_precip(self):
        """
        Convenience function for formatting precipitation condition values
        """
        val = self.values[self.COND_PRECIP]
        temp = float(self.values[self.AMBIENT_TEMP])
        if val == '0': return 'dry'
        if val == '1': return 'raining' if temp > 0 else 'snowing'
        if val == '2': return 'snowing'
        return '???'

    def _format_weather(self):
        """
        Convenience function for formatting overall weather condition values
        """
        val = self.values[self.COND_WEATHER]
        if val == '0': return 'unsafe'
        if val == '1': return 'safe'
        return '???'
    
    def _format_humidity(self):
        """
        Convenience function for formatting humidity condition values
        """
        val = self.values[self.COND_HUMIDITY]
        return self._format_lmh(val)  

    def _format_pressure(self):
        """
        Convenience function for formatting pressure condition values
        """
        val = self.values[self.COND_PRESSURE]
        return self._format_lmh(val)  

    def _format_voltage(self):
        """
        Convenience function for formatting voltage condition values
        """
        val = self.values[self.COND_VOLTAGE]
        return self._format_lmh(val)  

    def get_cond(self, key):
        """
        Convenience function for formatting condition values into human-readable strings

        Parameters
        ----------
        key : string
            EngineeringData condition Access key to format. e.g. `EngineeringData.COND_OVERCAST`

        Returns
        -------
        formatted_value : string
            Human readable interpretation of the condition enumerations

        Examples
        --------
        When `EngineeringData[COND_OVERCAST] == 0`

        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, vals = bcs.getENAll()
        >>> ed = EngineeringData(vals)
        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'clear'

        When `EngineeringData[COND_OVERCAST] == 1`
        
        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'cloudy'
        
        When `EngineeringData[COND_OVERCAST] == 2`

        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'vcloudy'

        

        """
        match key:
            case self.COND_OVERCAST:
                return self._format_overcast()
            case self.COND_WIND:
                return self._format_wind()
            case self.COND_BRIGHTNESS:
                return self._format_bright()
            case self.COND_PRECIP:  
                return self._format_precip()
            case self.COND_WEATHER: 
                return self._format_weather()
            case self.COND_HUMIDITY: 
                return self._format_humidity()
            case self.COND_PRESSURE:  
                return self._format_pressure()
            case self.COND_VOLTAGE:          
                return self._format_voltage()
        return "???"    
    
    keys_cond = [        
        COND_OVERCAST,
        COND_WIND,
        COND_BRIGHTNESS,
        COND_PRECIP,
        COND_WEATHER,
        COND_HUMIDITY,
        COND_PRESSURE,
        COND_VOLTAGE
    ]
    """List of EngineeringData condition keys"""

    keys = [
        WIND_HOT,
        WIND_COLD,
        WINDSPEED,
        PRESSURE,
        PRESSURE_TEMP,
        AMBIENT_TEMP,
        VOLTAGE,
        DAYLIGHT,
        SKY_AMBIENT,
        SKY_TEMP,
        CASE_TEMP,
        RAIN_RAW,
        RAIN_DPM,
        HUMIDITY,
        HUMIDITY_TEMP,
        DEWPOINT
    ]
    """List of EngineeringData parameter keys"""

    values = {}
    """
        Storage array for parsed values. 
        
        We advise you use array access on the storage class instance to access (see examples).

        Parameters
        ----------
        key : string
            Either an accessor key provided above (e.g. boltwood.EngineeringData.WIND_HOT or boltwood.EngineeringData.COND_OVERCAST)
            or the raw string that key represents

        Returns
        -------
        value : string
            The value of the requested key

        See Also
        --------
        Boltwood.getENAll()

        Examples
        --------
        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getENAll()
        >>> values = EngineeringData(raw_values)
        >>> values[EngineeringData.WIND_HOT]
        20
        
        >>> values[EngineeringData.COND_OVERCAST]
        0
        """

    def __init__(self, str):
        """
        Engineering Data string decoder
        
        Parameters
        ----------
        str : string
            Value portion of a `Boltwood.getENAll()` call

        Examples
        --------
        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCAll()
        >>> values = EngineeringData(raw_values)
        >>> print(f'{values[EngineeringData.HUMIDITY]}')
        ( 50.0, 1 )
        """
        self._parse(str)

    def _parse(self, str):
        """
        Enineering Data decoder

        Decodes the passed string from a space-delimited list of engineering data values and saves them
        in boltwood.EngineeringData.values

        Parameters
        ----------
        str : string
            Value portion of a Boltwood.getENAll() call
        """
        arr = str.split(' ')
        for i in range(0, len(self.keys)):
            self.values[self.keys[i]] = arr[i]
        for i in range(0, len(self.keys_cond)):
            self.values[self.keys_cond[i]] = arr[i+len(self.keys)]      

    def __getitem__(self, key):
        return self.values[key]
    
class Thresholds:
    CLEAR_CLOUDY     = 'clearCloudy'
    """Threshold/Safety Trigger accessor key for reporting cloudy when (sky temp - ambient temp) > threshold in degrees Celcius"""

    CLOUDY_VCLOUDY   = 'cloudyVeryCloudy'
    """Threshold/Safety Trigger accessor key for reporting vcloudy when (sky temp - ambient temp) > threshold in degrees Celcius"""

    CALM_WINDY       = 'calmWindy'
    """Threshold/Safety Trigger accessor key for reporting windy when windspeed > threshold in km/h"""

    WINDY_VWINDY     = 'windyVeryWindy'
    """Threshold/Safety Trigger accessor key for reporting vwindy when windspeed > threshold in km/h"""

    DARK_LIGHT       = 'darkLight'
    """Threshold/Safety Trigger accessor key for reporting light when brightness > threshold in %"""

    LIGHT_VLIGHT     = 'lightVeryLight'
    """Threshold/Safety Trigger accessor key for reporting vlight when brightness > threshold in %"""

    RAIN_SENSITIVITY = 'rainSensitivity'
    """Threshold/Safety Trigger accessor key for reporting rain hit when rain sensor duty cycle > threshold in microseconds"""

    RAIN_DPM         = 'rainDpmThreshold'
    """Threshold/Safety Trigger accessor key for reporting raining/snowing when dpm > threshold in drops/minute"""

    HUMIDITY_LOW     = 'humidityLow'
    """Threshold/Safety Trigger accessor key for reporting low humidity when humidity < threshold in %"""

    HUMIDITY_HIGH    = 'humidityHigh'
    """Threshold/Safety Trigger accessor key for reporting high humidity when humidity > threshold in %"""

    PRESSURE_LOW     = 'pressureLow'
    """Threshold/Safety Trigger accessor key for reporting low pressure when pressure < threshold in mBar"""

    PRESSURE_HIGH    = 'pressureHigh'
    """Threshold/Safety Trigger accessor key for reporting high pressure when pressure > threshold in mBar"""

    VOLTAGE_LOW      = 'voltageLow'
    """Threshold/Safety Trigger accessor key for reporting low voltage when voltage < threshold in V"""

    VOLTAGE_HIGH     = 'voltageHigh'
    """Threshold/Safety Trigger accessor key for reporting high voltage when voltage > threshold in V"""

    keys = [
        CLEAR_CLOUDY,     
        CLOUDY_VCLOUDY,      
        CALM_WINDY,       
        WINDY_VWINDY,        
        DARK_LIGHT,       
        LIGHT_VLIGHT,    
        RAIN_SENSITIVITY,
        RAIN_DPM,       
        HUMIDITY_LOW,      
        HUMIDITY_HIGH, 
        PRESSURE_LOW,      
        PRESSURE_HIGH,      
        VOLTAGE_LOW,       
        VOLTAGE_HIGH
    ]
    """List of Threshold/Safety Trigger accessor keys"""

    threshValue = {}
    """List of Threshold values indexed by Threshold/Safety Trigger accessor keys"""

    roofTrig = {}
    """List of Safety Trigger values indexed by Threshold/Safety Trigger accessor keys"""
    
    def __init__(self, str=''):
        """
        Threshold/Safety Trigger decoder class 

        Parameters
        ----------
        str : string
            The value result of a call to `boltwood.Boltwood.getOCThresholds()`

        See Also
        --------
        Boltwood.getOCThresholds()

        Examples
        --------
        >>> from boltwood import Boltwood, Thresholds
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCThresholds()
        >>> values = Thresholds(raw_values)
        >>> values.threshValue[Thresholds.CLEAR_CLOUDY] # Threshold value
        -10
        >>> values.roofTrig[Thresholds.CLEAR_CLOUDY] # Active as safety trigger
        0
        >>> values[Thresholds.CLEAR_CLOUDY] # Tuple of threshold value & safety trigger
        ("-10", 0)
        """
        self._parse(str)

    def _parse(self, str):
        if not str: return
        arr = str.split(" ")
        for i in range(0, len(self.keys)):
            self.threshValue[self.keys[i]] = arr[i]
            self.roofTrig[self.keys[i]] = arr[i + len(self.keys)]
                               
    def to_string(self):
        """
        Encodes values stored in this class instance into a string for transmission via boltwood.Boltwood.setOCThresholds()

        Examples
        --------
        >>> from boltwood import Boltwood, Thresholds
        >>> bcs = Boltwood("COM1") 
        >>> bcs.open()
        >>> # Fetch existing thresholds first
        >>> rc, raw_thresholds = bcs.getOCThresholds() 
        >>> # Parse the result
        >>> thresholds = Thresholds(raw_thresholds) 
        >>> # Add Thresholds.CLEAR_CLOUDY to list of active safety triggers
        >>> thresholds.roofTrig[Thresholds.CLEAR_CLOUDY] = 1 
        >>> # Send updated threshold structure to Boltwood, expect: (True, "")
        >>> bcs.setOCThresholds(thresholds.to_string())
        (True, "")

        Returns
        -------
        encoded_str : string
            The Threshold/Safety Trigger values stored in proper order for transmission to a BCSIII device over a serial connection
        """
        tmp = []
        for key in self.keys:
            tmp.append("{}".format(self.threshValue[key]))
        for key in self.keys:
            tmp.append("{}".format(self.roofTrig[key]))
        return ' '.join(tmp)
    
    def __getitem__(self, key):
        """
        Accessor for Thresholds/Safety Trigger entries

        Looks up a stored Threshold/Safety Trigger value by access key and returns it as a tuple

        Parameters
        ----------
        key : string
            Thresholds/Safety Trigger ccess key, or raw string representing the threshold to be accessed

        Returns
        -------
        threshold : string
            string-encoded value of the Thresholds

        safety_trigger : string
            string-encoded value of whether the Safety Trigger is active for the provided key ('1' for active, '0' for inactive)    
        """
        return (self.threshValue[key], self.roofTrig[key])
        
class Boltwood:
    _ser = serial.Serial()
    """Serial connection manager [Internal only]"""

    port = '/dev/ttyUSB0'
    """Serial port name, must be set before calling Boltwood.open()"""

    _dbg = False
    """Internal use only"""

    def __init__(self, port):
        """
        Class constructor for the Boltwood Cloud Sensor III python serial API

        Parameters
        ----------
        port : string
            Valid serial port hosting a Boltwood Cloud Sensor III
        """
        self.port = port

    def debug(self, val):
        """
        Reserved for factory use
        """
        self._dbg = val
    
    def __del__(self):
        if self._ser.is_open:
            self._ser.close()

    def open(self):
        """
        Opens a connection to the serial port provided in Boltwood class constructor.

        e.g.

        ```python
        from boltwood import Boltwood

        bcs = Boltwood("COM1")                          # for Windows machines
        bcs = Boltwood("/dev/ttyUSB0")                  # for Linux machines
        bcs = Boltwood("/dev/tty.usbserial-FTG6RCEJ")   # for MacOS machines

        bcs.open()

        # ... Do stuff with bcs ...
        ```
        """
        if self._ser.is_open:
            return

        if self._dbg: print("Connecting to BCSIII")            
        self._ser.port = self.port
        self._ser.baudrate = 9600
        self._ser.bytesize = serial.EIGHTBITS
        self._ser.parity = serial.PARITY_NONE
        self._ser.stopbits = serial.STOPBITS_ONE
        self._ser.timeout = 2
        self._ser.rtscts = False

        self._ser.rts = False
        self._ser.dtr = True
        self._ser.open()
        self._ser.rts = False

        if not self._dbg:
            return

    def close(self):
        """
        Closes an open serial connection. Executed automatically by Boltwood class destructor.
        """
        self._ser.close()
    
    def scan():
        """
        Lists all available serial ports on the host OS

        Returns
        -------
        ports : list
            An array of serial port designators

        Examples
        --------
        Windows

        >>> from boltwood import Boltwood
        >>> Boltwood.scan()
        [ "COM1", "COM3" ]
        
        Linux
        
        >>> Boltwood.scan()
        [ "/dev/ttyUSB0" ]

        MacOS

        >>> Boltwood.scan()
        [ "/dev/tty.usbserial-FT123456" ]

        """
        ports = serial_ports.comports()
        results = []
        for port in ports:
            results.append(port[0])
        return results

    def _exec(self, verb, key, param, val=''):
        if not self._ser.is_open:
            self.open()

        self._ser.reset_output_buffer()
        self._ser.reset_input_buffer()

        # construct the command
        dout = "{} {} {}".format(verb, key, param)
        if (val):
            dout = "{} {}".format(dout, val)
        dout = dout + '\n'

        if self._dbg: print('out: {}'.format(dout[0:-1]))        
        self._ser.write(dout.encode())
        din = self._ser.readline().decode()
        if self._dbg: print('in : {}'.format(din[0:-1]))
        if din[0] != '1' and din[0] != '0' and din[0] != '2':
            raise RuntimeError('Invalid response from BCSIII')
                  
        return din[0] == '0', din[2:-1]

    def get(self, key, param):
        """
        General-use interface accessor for readable properties      

        Parameters
        ----------
        key : string
            An interface shorthand key to access. e.g. "dd" for Device Descriptors
            
        param : string
            Parameter key to retrieve. e.g. "sta_ssid"

        Returns
        -------
        rc : bool
            False if successful, True otherwise
        
        val : string
            string-encoded requested value if successful, error message otherwise

        See Also
        --------
        Interfaces
        ObservingConditions
        SafetyMonitor
        DeviceDescriptor
        EngineeringData

        Examples
        --------
        >>> from boltwood import Boltwood, Interfaces, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
        (True, "-12.0")
        """
        return self._exec('g', key, param)

    def getOC(self, param):
        """
        Retrieve readable Observing Conditions interface values and threshold/safety trigger structure 

        Parameters
        ----------
        param : string
            Observing Conditions access key to read. e.g. ObservingConditions.TEMPERATURE

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Observing Conditions interface readings

        See Also
        --------
        ObservingConditions
        """
        return self.get('oc', param)

    def getSM(self, param):
        """
        Retrieve readable Safety Monitor interface values 
        
        Parameters
        ----------
        param : string
            Safety Monitor access key to retrieve. e.g. SafetyMonitor.IS_SAFE

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Safety Monitor interface readings
        
        See Also
        --------
        SafetyMonitor
        """
        return self.get('sm', param)

    def getDD(self, param):
        """
        Retrieve readable Device Descriptor interface values

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Device Descriptor access key to set. e.g. DeviceDescriptor.STA_SSID

        val : string
            Value to set. e.g. "MyWiFiSSID"

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        DeviceDescriptor
        """
        return self.get('dd', param)

    def getEN(self, param='all'):
        """
        Retrieve readable Enineering Data interface properties

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            EngineeringData access key to set. e.g. EngineeringData.STA_SSID

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        EngineeringData
        """
        return self.get('en', param)

    def put(self, key, param, val):
        """
        General-use interface accessor for writable properties

        Parameters
        ----------
        key : string
            An interface shorthand key to access. e.g. "dd" for Device Descriptors
            
        param : string
            Parameter key to set. e.g. "sta_ssid"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        Interfaces
        ObservingConditions
        SafetyMonitor
        DeviceDescriptor
        EngineeringData

        Examples
        --------
        >>> from boltwood import Boltwood, Interfaces, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
        (True, "")
        >>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE, 23)
        (False, "Invalid argument: property 'temperature' is read-only")
        """
        return self._exec('p', key, param, val)

    def putDD(self, param, val):
        """
        Set writable Device Descriptor values

        This method can be used to set writable device descriptor values such as:
        - sta_ssid
        - sta_pass

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Parameter key to set. e.g. "sta_ssid"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise
        """
        return self.put('dd', param, val)

    def putOC(self, param, val):
        """
        Set writable Observing Conditions values

        This method can be used to set writable observing condition values such as:
        - refresh
        - thresholds (see also: Boltwood.setOCThresholds())

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Parameter key to set. e.g. "thresholds"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise
        """
        return self.put('oc', param, val)

    def getSerial(self):
        """
        Retrieve Serial Number

        Fetches the device's factory-programmed serial number.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        serial : string
            Device's factory programmed serial number. e.g. "BCS3S24010203"
        """                       
        return self.getDD('serial')

    def getFWRev(self):
        """
        Retrieve Firmware Revision Number

        Fetches the device's firmware revision number. Requires firmware revision 12 or greater.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        revision : string
            Device's firmware revision number. e.g. "rev12"
        """
        return self.getDD('fwrev')

    def getSTAIP(self):
        """
        Retrieve DHCP-assigned WiFi Station IP Address

        Fetches the device's DHCP-assigned IP if connected to the programmed WiFi Station.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ip : string
            Device's DHCP-assigned Station IP if the device is connected to a WiFi station, empty string if not.
        """
        return self.getDD('sta_ip')

    def getSTASSID(self):
        """
        Retrieve Wireless Station SSID

        Fetches the device's programmed user-submitted wireless Station SSID.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ssid : string
            Device's programmed WiFi Station SSID if rc is True, garbage otherwise
        """
        return self.getDD('sta_ssid')

    def getAPSSID(self):
        """
        Retrieve Wireless Access Point SSID

        Fetches the device's unique wireless Accesss Point SSID.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ssid : string
            Device's factory-set Access Point SSID if rc is True, garbage otherwise
        """
        return self.getDD('ap_ssid')
    
    def getOCAll(self):
        """
        Retrieve Observing Conditions amalgamator 

        Shorthand for `Boltwood.getOC('all')`

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Observing Conditions interface readings

        See Also
        --------
        ObservingConditions
        Boltwood.getOC()
        """
        return self.getOC('all')

    def getSMAll(self):
        """
        Retrieve Safety Monitor amalgamator 

        Fetches all Safety Monitor interface parameters:
        A string-delimited list of all supported parameters as defined in the ASCOM Safety Monitor device interface.
        Currently:
         
        isSafe: 
            1 if device detects no configured safety condition failures. 0 otherwise.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Safety Monitor interface readings

        See Also
        --------
        SafetyMonitor
        Boltwood.getSM()
        """
        return self.getSM('all')

    def getENAll(self):
        """
        Retrieve Engineering Data amalgamator.

        Shorthand for `Boltwood.getEN('all')`.
        
        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Engineering Data interface readings

        See Also
        --------
        EngineeringData
        Boltwood.getEN()
        """
        return self.getEN('all')
    
    def getOCThresholds(self):
        """
        Retrieve user-programmed safety thresholds and trigger structure

        Shorthand for `Boltwood.getOC('thresholds')`

        Fetches a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
        The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
        when those thresholds are used to denote a safety exception.

        See Also
        --------
        Thresholds
        Boltwood.getOC()
        """
        return self.getOC('thresholds')

    def setOCThresholds(self, val):
        """
        Set user-programmed safety thresholds and trigger structure
        
        Sets a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
        The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
        when those thresholds are used to denote a safety exception.

        boltwood.Thresholds has been provided as a convenience wrapper to encode and decode this list in a programmatic way:

        See Also
        --------
        Thresholds
        Boltwood.putOC()
        """
        return self.putOC('thresholds', val)

Boltwood.scan = staticmethod(Boltwood.scan)

Classes

class Boltwood (port)

Class constructor for the Boltwood Cloud Sensor III python serial API

Parameters

port : string
Valid serial port hosting a Boltwood Cloud Sensor III
Expand source code
class Boltwood:
    _ser = serial.Serial()
    """Serial connection manager [Internal only]"""

    port = '/dev/ttyUSB0'
    """Serial port name, must be set before calling Boltwood.open()"""

    _dbg = False
    """Internal use only"""

    def __init__(self, port):
        """
        Class constructor for the Boltwood Cloud Sensor III python serial API

        Parameters
        ----------
        port : string
            Valid serial port hosting a Boltwood Cloud Sensor III
        """
        self.port = port

    def debug(self, val):
        """
        Reserved for factory use
        """
        self._dbg = val
    
    def __del__(self):
        if self._ser.is_open:
            self._ser.close()

    def open(self):
        """
        Opens a connection to the serial port provided in Boltwood class constructor.

        e.g.

        ```python
        from boltwood import Boltwood

        bcs = Boltwood("COM1")                          # for Windows machines
        bcs = Boltwood("/dev/ttyUSB0")                  # for Linux machines
        bcs = Boltwood("/dev/tty.usbserial-FTG6RCEJ")   # for MacOS machines

        bcs.open()

        # ... Do stuff with bcs ...
        ```
        """
        if self._ser.is_open:
            return

        if self._dbg: print("Connecting to BCSIII")            
        self._ser.port = self.port
        self._ser.baudrate = 9600
        self._ser.bytesize = serial.EIGHTBITS
        self._ser.parity = serial.PARITY_NONE
        self._ser.stopbits = serial.STOPBITS_ONE
        self._ser.timeout = 2
        self._ser.rtscts = False

        self._ser.rts = False
        self._ser.dtr = True
        self._ser.open()
        self._ser.rts = False

        if not self._dbg:
            return

    def close(self):
        """
        Closes an open serial connection. Executed automatically by Boltwood class destructor.
        """
        self._ser.close()
    
    def scan():
        """
        Lists all available serial ports on the host OS

        Returns
        -------
        ports : list
            An array of serial port designators

        Examples
        --------
        Windows

        >>> from boltwood import Boltwood
        >>> Boltwood.scan()
        [ "COM1", "COM3" ]
        
        Linux
        
        >>> Boltwood.scan()
        [ "/dev/ttyUSB0" ]

        MacOS

        >>> Boltwood.scan()
        [ "/dev/tty.usbserial-FT123456" ]

        """
        ports = serial_ports.comports()
        results = []
        for port in ports:
            results.append(port[0])
        return results

    def _exec(self, verb, key, param, val=''):
        if not self._ser.is_open:
            self.open()

        self._ser.reset_output_buffer()
        self._ser.reset_input_buffer()

        # construct the command
        dout = "{} {} {}".format(verb, key, param)
        if (val):
            dout = "{} {}".format(dout, val)
        dout = dout + '\n'

        if self._dbg: print('out: {}'.format(dout[0:-1]))        
        self._ser.write(dout.encode())
        din = self._ser.readline().decode()
        if self._dbg: print('in : {}'.format(din[0:-1]))
        if din[0] != '1' and din[0] != '0' and din[0] != '2':
            raise RuntimeError('Invalid response from BCSIII')
                  
        return din[0] == '0', din[2:-1]

    def get(self, key, param):
        """
        General-use interface accessor for readable properties      

        Parameters
        ----------
        key : string
            An interface shorthand key to access. e.g. "dd" for Device Descriptors
            
        param : string
            Parameter key to retrieve. e.g. "sta_ssid"

        Returns
        -------
        rc : bool
            False if successful, True otherwise
        
        val : string
            string-encoded requested value if successful, error message otherwise

        See Also
        --------
        Interfaces
        ObservingConditions
        SafetyMonitor
        DeviceDescriptor
        EngineeringData

        Examples
        --------
        >>> from boltwood import Boltwood, Interfaces, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
        (True, "-12.0")
        """
        return self._exec('g', key, param)

    def getOC(self, param):
        """
        Retrieve readable Observing Conditions interface values and threshold/safety trigger structure 

        Parameters
        ----------
        param : string
            Observing Conditions access key to read. e.g. ObservingConditions.TEMPERATURE

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Observing Conditions interface readings

        See Also
        --------
        ObservingConditions
        """
        return self.get('oc', param)

    def getSM(self, param):
        """
        Retrieve readable Safety Monitor interface values 
        
        Parameters
        ----------
        param : string
            Safety Monitor access key to retrieve. e.g. SafetyMonitor.IS_SAFE

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Safety Monitor interface readings
        
        See Also
        --------
        SafetyMonitor
        """
        return self.get('sm', param)

    def getDD(self, param):
        """
        Retrieve readable Device Descriptor interface values

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Device Descriptor access key to set. e.g. DeviceDescriptor.STA_SSID

        val : string
            Value to set. e.g. "MyWiFiSSID"

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        DeviceDescriptor
        """
        return self.get('dd', param)

    def getEN(self, param='all'):
        """
        Retrieve readable Enineering Data interface properties

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            EngineeringData access key to set. e.g. EngineeringData.STA_SSID

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        EngineeringData
        """
        return self.get('en', param)

    def put(self, key, param, val):
        """
        General-use interface accessor for writable properties

        Parameters
        ----------
        key : string
            An interface shorthand key to access. e.g. "dd" for Device Descriptors
            
        param : string
            Parameter key to set. e.g. "sta_ssid"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise

        See Also
        --------
        Interfaces
        ObservingConditions
        SafetyMonitor
        DeviceDescriptor
        EngineeringData

        Examples
        --------
        >>> from boltwood import Boltwood, Interfaces, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
        (True, "")
        >>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE, 23)
        (False, "Invalid argument: property 'temperature' is read-only")
        """
        return self._exec('p', key, param, val)

    def putDD(self, param, val):
        """
        Set writable Device Descriptor values

        This method can be used to set writable device descriptor values such as:
        - sta_ssid
        - sta_pass

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Parameter key to set. e.g. "sta_ssid"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise
        """
        return self.put('dd', param, val)

    def putOC(self, param, val):
        """
        Set writable Observing Conditions values

        This method can be used to set writable observing condition values such as:
        - refresh
        - thresholds (see also: Boltwood.setOCThresholds())

        Parameters
        ----------
        self : Boltwood
            Reference to a connected Boltwood object
        
        param : string
            Parameter key to set. e.g. "thresholds"

        val : string
            Value to set

        Returns
        -------
        rc : bool
            0 if successful, 1 otherwise
        
        val : string
            empty if successful, error message otherwise
        """
        return self.put('oc', param, val)

    def getSerial(self):
        """
        Retrieve Serial Number

        Fetches the device's factory-programmed serial number.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        serial : string
            Device's factory programmed serial number. e.g. "BCS3S24010203"
        """                       
        return self.getDD('serial')

    def getFWRev(self):
        """
        Retrieve Firmware Revision Number

        Fetches the device's firmware revision number. Requires firmware revision 12 or greater.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        revision : string
            Device's firmware revision number. e.g. "rev12"
        """
        return self.getDD('fwrev')

    def getSTAIP(self):
        """
        Retrieve DHCP-assigned WiFi Station IP Address

        Fetches the device's DHCP-assigned IP if connected to the programmed WiFi Station.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ip : string
            Device's DHCP-assigned Station IP if the device is connected to a WiFi station, empty string if not.
        """
        return self.getDD('sta_ip')

    def getSTASSID(self):
        """
        Retrieve Wireless Station SSID

        Fetches the device's programmed user-submitted wireless Station SSID.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ssid : string
            Device's programmed WiFi Station SSID if rc is True, garbage otherwise
        """
        return self.getDD('sta_ssid')

    def getAPSSID(self):
        """
        Retrieve Wireless Access Point SSID

        Fetches the device's unique wireless Accesss Point SSID.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        ssid : string
            Device's factory-set Access Point SSID if rc is True, garbage otherwise
        """
        return self.getDD('ap_ssid')
    
    def getOCAll(self):
        """
        Retrieve Observing Conditions amalgamator 

        Shorthand for `Boltwood.getOC('all')`

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Observing Conditions interface readings

        See Also
        --------
        ObservingConditions
        Boltwood.getOC()
        """
        return self.getOC('all')

    def getSMAll(self):
        """
        Retrieve Safety Monitor amalgamator 

        Fetches all Safety Monitor interface parameters:
        A string-delimited list of all supported parameters as defined in the ASCOM Safety Monitor device interface.
        Currently:
         
        isSafe: 
            1 if device detects no configured safety condition failures. 0 otherwise.

        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Safety Monitor interface readings

        See Also
        --------
        SafetyMonitor
        Boltwood.getSM()
        """
        return self.getSM('all')

    def getENAll(self):
        """
        Retrieve Engineering Data amalgamator.

        Shorthand for `Boltwood.getEN('all')`.
        
        Returns
        -------
        rc : bool
            True if successful, False otherwise

        status : string
            Device's current Engineering Data interface readings

        See Also
        --------
        EngineeringData
        Boltwood.getEN()
        """
        return self.getEN('all')
    
    def getOCThresholds(self):
        """
        Retrieve user-programmed safety thresholds and trigger structure

        Shorthand for `Boltwood.getOC('thresholds')`

        Fetches a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
        The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
        when those thresholds are used to denote a safety exception.

        See Also
        --------
        Thresholds
        Boltwood.getOC()
        """
        return self.getOC('thresholds')

    def setOCThresholds(self, val):
        """
        Set user-programmed safety thresholds and trigger structure
        
        Sets a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
        The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
        when those thresholds are used to denote a safety exception.

        boltwood.Thresholds has been provided as a convenience wrapper to encode and decode this list in a programmatic way:

        See Also
        --------
        Thresholds
        Boltwood.putOC()
        """
        return self.putOC('thresholds', val)

Class variables

var port

Serial port name, must be set before calling Boltwood.open()

Static methods

def scan()

Lists all available serial ports on the host OS

Returns

ports : list
An array of serial port designators

Examples

Windows

>>> from boltwood import Boltwood
>>> Boltwood.scan()
[ "COM1", "COM3" ]

Linux

>>> Boltwood.scan()
[ "/dev/ttyUSB0" ]

MacOS

>>> Boltwood.scan()
[ "/dev/tty.usbserial-FT123456" ]
Expand source code
def scan():
    """
    Lists all available serial ports on the host OS

    Returns
    -------
    ports : list
        An array of serial port designators

    Examples
    --------
    Windows

    >>> from boltwood import Boltwood
    >>> Boltwood.scan()
    [ "COM1", "COM3" ]
    
    Linux
    
    >>> Boltwood.scan()
    [ "/dev/ttyUSB0" ]

    MacOS

    >>> Boltwood.scan()
    [ "/dev/tty.usbserial-FT123456" ]

    """
    ports = serial_ports.comports()
    results = []
    for port in ports:
        results.append(port[0])
    return results

Methods

def close(self)

Closes an open serial connection. Executed automatically by Boltwood class destructor.

Expand source code
def close(self):
    """
    Closes an open serial connection. Executed automatically by Boltwood class destructor.
    """
    self._ser.close()
def debug(self, val)

Reserved for factory use

Expand source code
def debug(self, val):
    """
    Reserved for factory use
    """
    self._dbg = val
def get(self, key, param)

General-use interface accessor for readable properties

Parameters

key : string
An interface shorthand key to access. e.g. "dd" for Device Descriptors
param : string
Parameter key to retrieve. e.g. "sta_ssid"

Returns

rc : bool
False if successful, True otherwise
val : string
string-encoded requested value if successful, error message otherwise

See Also

Interfaces ObservingConditions SafetyMonitor DeviceDescriptor EngineeringData

Examples

>>> from boltwood import Boltwood, Interfaces, ObservingConditions
>>> bcs = Boltwood("COM1")
>>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
(True, "-12.0")
Expand source code
def get(self, key, param):
    """
    General-use interface accessor for readable properties      

    Parameters
    ----------
    key : string
        An interface shorthand key to access. e.g. "dd" for Device Descriptors
        
    param : string
        Parameter key to retrieve. e.g. "sta_ssid"

    Returns
    -------
    rc : bool
        False if successful, True otherwise
    
    val : string
        string-encoded requested value if successful, error message otherwise

    See Also
    --------
    Interfaces
    ObservingConditions
    SafetyMonitor
    DeviceDescriptor
    EngineeringData

    Examples
    --------
    >>> from boltwood import Boltwood, Interfaces, ObservingConditions
    >>> bcs = Boltwood("COM1")
    >>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
    (True, "-12.0")
    """
    return self._exec('g', key, param)
def getAPSSID(self)

Retrieve Wireless Access Point SSID

Fetches the device's unique wireless Accesss Point SSID.

Returns

rc : bool
True if successful, False otherwise
ssid : string
Device's factory-set Access Point SSID if rc is True, garbage otherwise
Expand source code
def getAPSSID(self):
    """
    Retrieve Wireless Access Point SSID

    Fetches the device's unique wireless Accesss Point SSID.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    ssid : string
        Device's factory-set Access Point SSID if rc is True, garbage otherwise
    """
    return self.getDD('ap_ssid')
def getDD(self, param)

Retrieve readable Device Descriptor interface values

Parameters

self : Boltwood
Reference to a connected Boltwood object
param : string
Device Descriptor access key to set. e.g. DeviceDescriptor.STA_SSID
val : string
Value to set. e.g. "MyWiFiSSID"

Returns

rc : bool
0 if successful, 1 otherwise
val : string
empty if successful, error message otherwise

See Also

DeviceDescriptor

Expand source code
def getDD(self, param):
    """
    Retrieve readable Device Descriptor interface values

    Parameters
    ----------
    self : Boltwood
        Reference to a connected Boltwood object
    
    param : string
        Device Descriptor access key to set. e.g. DeviceDescriptor.STA_SSID

    val : string
        Value to set. e.g. "MyWiFiSSID"

    Returns
    -------
    rc : bool
        0 if successful, 1 otherwise
    
    val : string
        empty if successful, error message otherwise

    See Also
    --------
    DeviceDescriptor
    """
    return self.get('dd', param)
def getEN(self, param='all')

Retrieve readable Enineering Data interface properties

Parameters

self : Boltwood
Reference to a connected Boltwood object
param : string
EngineeringData access key to set. e.g. EngineeringData.STA_SSID
val : string
Value to set

Returns

rc : bool
0 if successful, 1 otherwise
val : string
empty if successful, error message otherwise

See Also

EngineeringData

Expand source code
def getEN(self, param='all'):
    """
    Retrieve readable Enineering Data interface properties

    Parameters
    ----------
    self : Boltwood
        Reference to a connected Boltwood object
    
    param : string
        EngineeringData access key to set. e.g. EngineeringData.STA_SSID

    val : string
        Value to set

    Returns
    -------
    rc : bool
        0 if successful, 1 otherwise
    
    val : string
        empty if successful, error message otherwise

    See Also
    --------
    EngineeringData
    """
    return self.get('en', param)
def getENAll(self)

Retrieve Engineering Data amalgamator.

Shorthand for Boltwood.getEN('all').

Returns

rc : bool
True if successful, False otherwise
status : string
Device's current Engineering Data interface readings

See Also

EngineeringData Boltwood.getEN()

Expand source code
def getENAll(self):
    """
    Retrieve Engineering Data amalgamator.

    Shorthand for `Boltwood.getEN('all')`.
    
    Returns
    -------
    rc : bool
        True if successful, False otherwise

    status : string
        Device's current Engineering Data interface readings

    See Also
    --------
    EngineeringData
    Boltwood.getEN()
    """
    return self.getEN('all')
def getFWRev(self)

Retrieve Firmware Revision Number

Fetches the device's firmware revision number. Requires firmware revision 12 or greater.

Returns

rc : bool
True if successful, False otherwise
revision : string
Device's firmware revision number. e.g. "rev12"
Expand source code
def getFWRev(self):
    """
    Retrieve Firmware Revision Number

    Fetches the device's firmware revision number. Requires firmware revision 12 or greater.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    revision : string
        Device's firmware revision number. e.g. "rev12"
    """
    return self.getDD('fwrev')
def getOC(self, param)

Retrieve readable Observing Conditions interface values and threshold/safety trigger structure

Parameters

param : string
Observing Conditions access key to read. e.g. ObservingConditions.TEMPERATURE

Returns

rc : bool
True if successful, False otherwise
status : string
Device's current Observing Conditions interface readings

See Also

ObservingConditions

Expand source code
def getOC(self, param):
    """
    Retrieve readable Observing Conditions interface values and threshold/safety trigger structure 

    Parameters
    ----------
    param : string
        Observing Conditions access key to read. e.g. ObservingConditions.TEMPERATURE

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    status : string
        Device's current Observing Conditions interface readings

    See Also
    --------
    ObservingConditions
    """
    return self.get('oc', param)
def getOCAll(self)

Retrieve Observing Conditions amalgamator

Shorthand for Boltwood.getOC('all')

Returns

rc : bool
True if successful, False otherwise
status : string
Device's current Observing Conditions interface readings

See Also

ObservingConditions Boltwood.getOC()

Expand source code
def getOCAll(self):
    """
    Retrieve Observing Conditions amalgamator 

    Shorthand for `Boltwood.getOC('all')`

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    status : string
        Device's current Observing Conditions interface readings

    See Also
    --------
    ObservingConditions
    Boltwood.getOC()
    """
    return self.getOC('all')
def getOCThresholds(self)

Retrieve user-programmed safety thresholds and trigger structure

Shorthand for Boltwood.getOC('thresholds')

Fetches a space-delimited list of internal safety thresholds and whether they're used in the roof trigger. The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) when those thresholds are used to denote a safety exception.

See Also

Thresholds Boltwood.getOC()

Expand source code
def getOCThresholds(self):
    """
    Retrieve user-programmed safety thresholds and trigger structure

    Shorthand for `Boltwood.getOC('thresholds')`

    Fetches a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
    The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
    when those thresholds are used to denote a safety exception.

    See Also
    --------
    Thresholds
    Boltwood.getOC()
    """
    return self.getOC('thresholds')
def getSM(self, param)

Retrieve readable Safety Monitor interface values

Parameters

param : string
Safety Monitor access key to retrieve. e.g. SafetyMonitor.IS_SAFE

Returns

rc : bool
True if successful, False otherwise
status : string
Device's current Safety Monitor interface readings

See Also

SafetyMonitor

Expand source code
def getSM(self, param):
    """
    Retrieve readable Safety Monitor interface values 
    
    Parameters
    ----------
    param : string
        Safety Monitor access key to retrieve. e.g. SafetyMonitor.IS_SAFE

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    status : string
        Device's current Safety Monitor interface readings
    
    See Also
    --------
    SafetyMonitor
    """
    return self.get('sm', param)
def getSMAll(self)

Retrieve Safety Monitor amalgamator

Fetches all Safety Monitor interface parameters: A string-delimited list of all supported parameters as defined in the ASCOM Safety Monitor device interface. Currently:

isSafe: 1 if device detects no configured safety condition failures. 0 otherwise.

Returns

rc : bool
True if successful, False otherwise
status : string
Device's current Safety Monitor interface readings

See Also

SafetyMonitor Boltwood.getSM()

Expand source code
def getSMAll(self):
    """
    Retrieve Safety Monitor amalgamator 

    Fetches all Safety Monitor interface parameters:
    A string-delimited list of all supported parameters as defined in the ASCOM Safety Monitor device interface.
    Currently:
     
    isSafe: 
        1 if device detects no configured safety condition failures. 0 otherwise.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    status : string
        Device's current Safety Monitor interface readings

    See Also
    --------
    SafetyMonitor
    Boltwood.getSM()
    """
    return self.getSM('all')
def getSTAIP(self)

Retrieve DHCP-assigned WiFi Station IP Address

Fetches the device's DHCP-assigned IP if connected to the programmed WiFi Station.

Returns

rc : bool
True if successful, False otherwise
ip : string
Device's DHCP-assigned Station IP if the device is connected to a WiFi station, empty string if not.
Expand source code
def getSTAIP(self):
    """
    Retrieve DHCP-assigned WiFi Station IP Address

    Fetches the device's DHCP-assigned IP if connected to the programmed WiFi Station.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    ip : string
        Device's DHCP-assigned Station IP if the device is connected to a WiFi station, empty string if not.
    """
    return self.getDD('sta_ip')
def getSTASSID(self)

Retrieve Wireless Station SSID

Fetches the device's programmed user-submitted wireless Station SSID.

Returns

rc : bool
True if successful, False otherwise
ssid : string
Device's programmed WiFi Station SSID if rc is True, garbage otherwise
Expand source code
def getSTASSID(self):
    """
    Retrieve Wireless Station SSID

    Fetches the device's programmed user-submitted wireless Station SSID.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    ssid : string
        Device's programmed WiFi Station SSID if rc is True, garbage otherwise
    """
    return self.getDD('sta_ssid')
def getSerial(self)

Retrieve Serial Number

Fetches the device's factory-programmed serial number.

Returns

rc : bool
True if successful, False otherwise
serial : string
Device's factory programmed serial number. e.g. "BCS3S24010203"
Expand source code
def getSerial(self):
    """
    Retrieve Serial Number

    Fetches the device's factory-programmed serial number.

    Returns
    -------
    rc : bool
        True if successful, False otherwise

    serial : string
        Device's factory programmed serial number. e.g. "BCS3S24010203"
    """                       
    return self.getDD('serial')
def open(self)

Opens a connection to the serial port provided in Boltwood class constructor.

e.g.

from boltwood import Boltwood

bcs = Boltwood("COM1")                          # for Windows machines
bcs = Boltwood("/dev/ttyUSB0")                  # for Linux machines
bcs = Boltwood("/dev/tty.usbserial-FTG6RCEJ")   # for MacOS machines

bcs.open()

# ... Do stuff with bcs ...
Expand source code
def open(self):
    """
    Opens a connection to the serial port provided in Boltwood class constructor.

    e.g.

    ```python
    from boltwood import Boltwood

    bcs = Boltwood("COM1")                          # for Windows machines
    bcs = Boltwood("/dev/ttyUSB0")                  # for Linux machines
    bcs = Boltwood("/dev/tty.usbserial-FTG6RCEJ")   # for MacOS machines

    bcs.open()

    # ... Do stuff with bcs ...
    ```
    """
    if self._ser.is_open:
        return

    if self._dbg: print("Connecting to BCSIII")            
    self._ser.port = self.port
    self._ser.baudrate = 9600
    self._ser.bytesize = serial.EIGHTBITS
    self._ser.parity = serial.PARITY_NONE
    self._ser.stopbits = serial.STOPBITS_ONE
    self._ser.timeout = 2
    self._ser.rtscts = False

    self._ser.rts = False
    self._ser.dtr = True
    self._ser.open()
    self._ser.rts = False

    if not self._dbg:
        return
def put(self, key, param, val)

General-use interface accessor for writable properties

Parameters

key : string
An interface shorthand key to access. e.g. "dd" for Device Descriptors
param : string
Parameter key to set. e.g. "sta_ssid"
val : string
Value to set

Returns

rc : bool
0 if successful, 1 otherwise
val : string
empty if successful, error message otherwise

See Also

Interfaces ObservingConditions SafetyMonitor DeviceDescriptor EngineeringData

Examples

>>> from boltwood import Boltwood, Interfaces, ObservingConditions
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
(True, "")
>>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE, 23)
(False, "Invalid argument: property 'temperature' is read-only")
Expand source code
def put(self, key, param, val):
    """
    General-use interface accessor for writable properties

    Parameters
    ----------
    key : string
        An interface shorthand key to access. e.g. "dd" for Device Descriptors
        
    param : string
        Parameter key to set. e.g. "sta_ssid"

    val : string
        Value to set

    Returns
    -------
    rc : bool
        0 if successful, 1 otherwise
    
    val : string
        empty if successful, error message otherwise

    See Also
    --------
    Interfaces
    ObservingConditions
    SafetyMonitor
    DeviceDescriptor
    EngineeringData

    Examples
    --------
    >>> from boltwood import Boltwood, Interfaces, ObservingConditions
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
    (True, "")
    >>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE, 23)
    (False, "Invalid argument: property 'temperature' is read-only")
    """
    return self._exec('p', key, param, val)
def putDD(self, param, val)

Set writable Device Descriptor values

This method can be used to set writable device descriptor values such as: - sta_ssid - sta_pass

Parameters

self : Boltwood
Reference to a connected Boltwood object
param : string
Parameter key to set. e.g. "sta_ssid"
val : string
Value to set

Returns

rc : bool
0 if successful, 1 otherwise
val : string
empty if successful, error message otherwise
Expand source code
def putDD(self, param, val):
    """
    Set writable Device Descriptor values

    This method can be used to set writable device descriptor values such as:
    - sta_ssid
    - sta_pass

    Parameters
    ----------
    self : Boltwood
        Reference to a connected Boltwood object
    
    param : string
        Parameter key to set. e.g. "sta_ssid"

    val : string
        Value to set

    Returns
    -------
    rc : bool
        0 if successful, 1 otherwise
    
    val : string
        empty if successful, error message otherwise
    """
    return self.put('dd', param, val)
def putOC(self, param, val)

Set writable Observing Conditions values

This method can be used to set writable observing condition values such as: - refresh - thresholds (see also: Boltwood.setOCThresholds())

Parameters

self : Boltwood
Reference to a connected Boltwood object
param : string
Parameter key to set. e.g. "thresholds"
val : string
Value to set

Returns

rc : bool
0 if successful, 1 otherwise
val : string
empty if successful, error message otherwise
Expand source code
def putOC(self, param, val):
    """
    Set writable Observing Conditions values

    This method can be used to set writable observing condition values such as:
    - refresh
    - thresholds (see also: Boltwood.setOCThresholds())

    Parameters
    ----------
    self : Boltwood
        Reference to a connected Boltwood object
    
    param : string
        Parameter key to set. e.g. "thresholds"

    val : string
        Value to set

    Returns
    -------
    rc : bool
        0 if successful, 1 otherwise
    
    val : string
        empty if successful, error message otherwise
    """
    return self.put('oc', param, val)
def setOCThresholds(self, val)

Set user-programmed safety thresholds and trigger structure

Sets a space-delimited list of internal safety thresholds and whether they're used in the roof trigger. The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) when those thresholds are used to denote a safety exception.

boltwood.Thresholds has been provided as a convenience wrapper to encode and decode this list in a programmatic way:

See Also

Thresholds Boltwood.putOC()

Expand source code
def setOCThresholds(self, val):
    """
    Set user-programmed safety thresholds and trigger structure
    
    Sets a space-delimited list of internal safety thresholds and whether they're used in the roof trigger.
    The thesholds are listed in the following order as floats, then again as booleans (0 for off, 1 for on) 
    when those thresholds are used to denote a safety exception.

    boltwood.Thresholds has been provided as a convenience wrapper to encode and decode this list in a programmatic way:

    See Also
    --------
    Thresholds
    Boltwood.putOC()
    """
    return self.putOC('thresholds', val)
class DeviceDescriptor

A list of access keys when accessing data via Boltwood.getDD() or Boltwood.putDD()

Expand source code
class DeviceDescriptor:
    """
    A list of access keys when accessing data via `Boltwood.getDD()` or `Boltwood.putDD()`
    """

    SERIAL = 'serial'
    """Access key for the device's unique serial number (read-only)"""

    FW_REVISION = "fwrev"
    """Access key for the device's firmware revision number (read-only)"""

    STA_IP = "sta_ip"
    """Access key for the device's WiFi Station DHCP-assigned IP address (read-only)"""

    STA_SSID = "sta_ssid"
    """Access key for the device's WiFi Station SSID (network name)"""

    STA_PASS = "sta_pass"
    """Access key for the device's WiFi Station passphrase (write-only)"""

    AP_SSID = "ap_ssid"
    """Access key for the device's factory-assigned WiFi Access Point SSID (read-only)"""

Class variables

var AP_SSID

Access key for the device's factory-assigned WiFi Access Point SSID (read-only)

var FW_REVISION

Access key for the device's firmware revision number (read-only)

var SERIAL

Access key for the device's unique serial number (read-only)

var STA_IP

Access key for the device's WiFi Station DHCP-assigned IP address (read-only)

var STA_PASS

Access key for the device's WiFi Station passphrase (write-only)

var STA_SSID

Access key for the device's WiFi Station SSID (network name)

class EngineeringData (str)

Engineering Data string decoder

Parameters

str : string
Value portion of a Boltwood.getENAll() call

Examples

>>> from boltwood import Boltwood, EngineeringData
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> rc, raw_values = bcs.getOCAll()
>>> values = EngineeringData(raw_values)
>>> print(f'{values[EngineeringData.HUMIDITY]}')
( 50.0, 1 )
Expand source code
class EngineeringData:
    WIND_COLD       = 'windColdSensor' 
    """Access key for EngineeringData Windspeed sensor cold stock temperature measurement (read-only)"""

    WIND_HOT        = 'windHotSensor'  
    """Access key for EngineeringData Windspeed sensor hot stock temperature measurement (read-only)"""

    WINDSPEED       = 'windSpeed'      
    """Access key for EngineeringData Windspeed sensor measurement (read-only)"""

    PRESSURE        = 'pressure'       
    """Access key for EngineeringData Pressure sensor measurement (read-only)"""

    PRESSURE_TEMP   = 'pressureTemp'   
    """Access key for EngineeringData Pressure sensor temperature measurement (read-only)"""

    AMBIENT_TEMP    = 'ambientTemp'
    """Access key for EngineeringData Ambient Temperature sensor temperature measurement (read-only)"""

    VOLTAGE         = 'voltage'        
    """Access key for EngineeringData Power Supply Voltage sensor measurement (read-only)"""

    DAYLIGHT        = 'daylightSensor' 
    """Access key for EngineeringData Daylight sensor measurement (read-only)"""

    SKY_AMBIENT     = 'skyAmbientTemp' 
    """Access key for EngineeringData Sky Temperature minus Ambient Temperature measurement (read-only)"""

    SKY_TEMP        = 'skyTemp'        
    """Access key for EngineeringData Sky Temperature sensor measurement (read-only)"""

    CASE_TEMP       = 'caseTemp'       
    """Access key for EngineeringData Case Temperature sensor measurement (read-only)"""

    RAIN_RAW        = 'rainRaw'        
    """Access key for EngineeringData Rain sensor's instantaneous raw drop sample (read-only)"""

    RAIN_DPM        = 'rainDpm'        
    """Access key for EngineeringData Rain sensor drops/minute measurement (read-only)"""

    HUMIDITY        = 'humidity'       
    """Access key for EngineeringData Humidity sensor measurement (read-only)"""

    HUMIDITY_TEMP   = 'humidityTemp'   
    """Access key for EngineeringData Humidity sensor onboard temperature measurement (read-only)"""

    DEWPOINT        = 'dewpoint'      
    """Access key for EngineeringData Dewpoint sensor calculated value (read-only)"""

    COND_OVERCAST   = 'condOvercast'
    """Access key for EngineeringData Overcast condition state (read-only)"""

    COND_WIND       = 'condWind'
    """Access key for EngineeringData Windy condition state (read-only)"""

    COND_BRIGHTNESS = 'condBrightness'
    """Access key for EngineeringData Brightness condition state (read-only)"""

    COND_PRECIP     = 'condPrecipitation'
    """Access key for EngineeringData Precipitation condition state (read-only)"""

    COND_WEATHER    = 'condWeather'
    """Access key for EngineeringData Overall Weather condition state (read-only)"""

    COND_HUMIDITY   = 'condHumidity'
    """Access key for EngineeringData Humidity condition state (read-only)"""

    COND_PRESSURE   = 'condPressure'
    """Access key for EngineeringData Pressure condition state (read-only)"""

    COND_VOLTAGE    = 'condVoltage'
    """Access key for EngineeringData Voltage condition state (read-only)"""

    def _format_lmh(self, val):
        """
        Convenience function for formatting low/medium/high condition values
        """
        if val == '0': return "low"
        if val == '1': return "med"
        if val == '2': return "high"
        return "???"

    def _format_overcast(self):
        """
        Convenience function for formatting overcast condition values
        """
        val = self.values[self.COND_OVERCAST]
        if val == '0': return 'clear'
        if val == '1': return 'cloudy'
        if val == '2': return 'vcloudy'
        return '???'

    def _format_wind(self):
        """
        Convenience function for formatting windy condition values
        """
        val = self.values[self.COND_WIND]
        if val == '0': return 'calm'
        if val == '1': return 'windy'
        if val == '2': return 'vwindy'
        return '???'

    def _format_bright(self):
        """
        Convenience function for formatting brightness condition values
        """
        val = self.values[self.COND_BRIGHTNESS]
        if val == '0': return 'dark'
        if val == '1': return 'light'
        if val == '2': return 'vlight'
        return '???'

    def _format_precip(self):
        """
        Convenience function for formatting precipitation condition values
        """
        val = self.values[self.COND_PRECIP]
        temp = float(self.values[self.AMBIENT_TEMP])
        if val == '0': return 'dry'
        if val == '1': return 'raining' if temp > 0 else 'snowing'
        if val == '2': return 'snowing'
        return '???'

    def _format_weather(self):
        """
        Convenience function for formatting overall weather condition values
        """
        val = self.values[self.COND_WEATHER]
        if val == '0': return 'unsafe'
        if val == '1': return 'safe'
        return '???'
    
    def _format_humidity(self):
        """
        Convenience function for formatting humidity condition values
        """
        val = self.values[self.COND_HUMIDITY]
        return self._format_lmh(val)  

    def _format_pressure(self):
        """
        Convenience function for formatting pressure condition values
        """
        val = self.values[self.COND_PRESSURE]
        return self._format_lmh(val)  

    def _format_voltage(self):
        """
        Convenience function for formatting voltage condition values
        """
        val = self.values[self.COND_VOLTAGE]
        return self._format_lmh(val)  

    def get_cond(self, key):
        """
        Convenience function for formatting condition values into human-readable strings

        Parameters
        ----------
        key : string
            EngineeringData condition Access key to format. e.g. `EngineeringData.COND_OVERCAST`

        Returns
        -------
        formatted_value : string
            Human readable interpretation of the condition enumerations

        Examples
        --------
        When `EngineeringData[COND_OVERCAST] == 0`

        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, vals = bcs.getENAll()
        >>> ed = EngineeringData(vals)
        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'clear'

        When `EngineeringData[COND_OVERCAST] == 1`
        
        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'cloudy'
        
        When `EngineeringData[COND_OVERCAST] == 2`

        >>> ed.get_cond(EngineeringData.COND_OVERCAST)
        'vcloudy'

        

        """
        match key:
            case self.COND_OVERCAST:
                return self._format_overcast()
            case self.COND_WIND:
                return self._format_wind()
            case self.COND_BRIGHTNESS:
                return self._format_bright()
            case self.COND_PRECIP:  
                return self._format_precip()
            case self.COND_WEATHER: 
                return self._format_weather()
            case self.COND_HUMIDITY: 
                return self._format_humidity()
            case self.COND_PRESSURE:  
                return self._format_pressure()
            case self.COND_VOLTAGE:          
                return self._format_voltage()
        return "???"    
    
    keys_cond = [        
        COND_OVERCAST,
        COND_WIND,
        COND_BRIGHTNESS,
        COND_PRECIP,
        COND_WEATHER,
        COND_HUMIDITY,
        COND_PRESSURE,
        COND_VOLTAGE
    ]
    """List of EngineeringData condition keys"""

    keys = [
        WIND_HOT,
        WIND_COLD,
        WINDSPEED,
        PRESSURE,
        PRESSURE_TEMP,
        AMBIENT_TEMP,
        VOLTAGE,
        DAYLIGHT,
        SKY_AMBIENT,
        SKY_TEMP,
        CASE_TEMP,
        RAIN_RAW,
        RAIN_DPM,
        HUMIDITY,
        HUMIDITY_TEMP,
        DEWPOINT
    ]
    """List of EngineeringData parameter keys"""

    values = {}
    """
        Storage array for parsed values. 
        
        We advise you use array access on the storage class instance to access (see examples).

        Parameters
        ----------
        key : string
            Either an accessor key provided above (e.g. boltwood.EngineeringData.WIND_HOT or boltwood.EngineeringData.COND_OVERCAST)
            or the raw string that key represents

        Returns
        -------
        value : string
            The value of the requested key

        See Also
        --------
        Boltwood.getENAll()

        Examples
        --------
        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getENAll()
        >>> values = EngineeringData(raw_values)
        >>> values[EngineeringData.WIND_HOT]
        20
        
        >>> values[EngineeringData.COND_OVERCAST]
        0
        """

    def __init__(self, str):
        """
        Engineering Data string decoder
        
        Parameters
        ----------
        str : string
            Value portion of a `Boltwood.getENAll()` call

        Examples
        --------
        >>> from boltwood import Boltwood, EngineeringData
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCAll()
        >>> values = EngineeringData(raw_values)
        >>> print(f'{values[EngineeringData.HUMIDITY]}')
        ( 50.0, 1 )
        """
        self._parse(str)

    def _parse(self, str):
        """
        Enineering Data decoder

        Decodes the passed string from a space-delimited list of engineering data values and saves them
        in boltwood.EngineeringData.values

        Parameters
        ----------
        str : string
            Value portion of a Boltwood.getENAll() call
        """
        arr = str.split(' ')
        for i in range(0, len(self.keys)):
            self.values[self.keys[i]] = arr[i]
        for i in range(0, len(self.keys_cond)):
            self.values[self.keys_cond[i]] = arr[i+len(self.keys)]      

    def __getitem__(self, key):
        return self.values[key]

Class variables

var AMBIENT_TEMP

Access key for EngineeringData Ambient Temperature sensor temperature measurement (read-only)

var CASE_TEMP

Access key for EngineeringData Case Temperature sensor measurement (read-only)

var COND_BRIGHTNESS

Access key for EngineeringData Brightness condition state (read-only)

var COND_HUMIDITY

Access key for EngineeringData Humidity condition state (read-only)

var COND_OVERCAST

Access key for EngineeringData Overcast condition state (read-only)

var COND_PRECIP

Access key for EngineeringData Precipitation condition state (read-only)

var COND_PRESSURE

Access key for EngineeringData Pressure condition state (read-only)

var COND_VOLTAGE

Access key for EngineeringData Voltage condition state (read-only)

var COND_WEATHER

Access key for EngineeringData Overall Weather condition state (read-only)

var COND_WIND

Access key for EngineeringData Windy condition state (read-only)

var DAYLIGHT

Access key for EngineeringData Daylight sensor measurement (read-only)

var DEWPOINT

Access key for EngineeringData Dewpoint sensor calculated value (read-only)

var HUMIDITY

Access key for EngineeringData Humidity sensor measurement (read-only)

var HUMIDITY_TEMP

Access key for EngineeringData Humidity sensor onboard temperature measurement (read-only)

var PRESSURE

Access key for EngineeringData Pressure sensor measurement (read-only)

var PRESSURE_TEMP

Access key for EngineeringData Pressure sensor temperature measurement (read-only)

var RAIN_DPM

Access key for EngineeringData Rain sensor drops/minute measurement (read-only)

var RAIN_RAW

Access key for EngineeringData Rain sensor's instantaneous raw drop sample (read-only)

var SKY_AMBIENT

Access key for EngineeringData Sky Temperature minus Ambient Temperature measurement (read-only)

var SKY_TEMP

Access key for EngineeringData Sky Temperature sensor measurement (read-only)

var VOLTAGE

Access key for EngineeringData Power Supply Voltage sensor measurement (read-only)

var WINDSPEED

Access key for EngineeringData Windspeed sensor measurement (read-only)

var WIND_COLD

Access key for EngineeringData Windspeed sensor cold stock temperature measurement (read-only)

var WIND_HOT

Access key for EngineeringData Windspeed sensor hot stock temperature measurement (read-only)

var keys

List of EngineeringData parameter keys

var keys_cond

List of EngineeringData condition keys

var values

Storage array for parsed values.

We advise you use array access on the storage class instance to access (see examples).

Parameters

key : string
Either an accessor key provided above (e.g. boltwood.EngineeringData.WIND_HOT or boltwood.EngineeringData.COND_OVERCAST) or the raw string that key represents

Returns

value : string
The value of the requested key

See Also

Boltwood.getENAll()

Examples

>>> from boltwood import Boltwood, EngineeringData
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> rc, raw_values = bcs.getENAll()
>>> values = EngineeringData(raw_values)
>>> values[EngineeringData.WIND_HOT]
20
>>> values[EngineeringData.COND_OVERCAST]
0

Methods

def get_cond(self, key)

Convenience function for formatting condition values into human-readable strings

Parameters

key : string
EngineeringData condition Access key to format. e.g. EngineeringData.COND_OVERCAST

Returns

formatted_value : string
Human readable interpretation of the condition enumerations

Examples

When EngineeringData[COND_OVERCAST] == 0

>>> from boltwood import Boltwood, EngineeringData
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> rc, vals = bcs.getENAll()
>>> ed = EngineeringData(vals)
>>> ed.get_cond(EngineeringData.COND_OVERCAST)
'clear'

When EngineeringData[COND_OVERCAST] == 1

>>> ed.get_cond(EngineeringData.COND_OVERCAST)
'cloudy'

When EngineeringData[COND_OVERCAST] == 2

>>> ed.get_cond(EngineeringData.COND_OVERCAST)
'vcloudy'
Expand source code
def get_cond(self, key):
    """
    Convenience function for formatting condition values into human-readable strings

    Parameters
    ----------
    key : string
        EngineeringData condition Access key to format. e.g. `EngineeringData.COND_OVERCAST`

    Returns
    -------
    formatted_value : string
        Human readable interpretation of the condition enumerations

    Examples
    --------
    When `EngineeringData[COND_OVERCAST] == 0`

    >>> from boltwood import Boltwood, EngineeringData
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> rc, vals = bcs.getENAll()
    >>> ed = EngineeringData(vals)
    >>> ed.get_cond(EngineeringData.COND_OVERCAST)
    'clear'

    When `EngineeringData[COND_OVERCAST] == 1`
    
    >>> ed.get_cond(EngineeringData.COND_OVERCAST)
    'cloudy'
    
    When `EngineeringData[COND_OVERCAST] == 2`

    >>> ed.get_cond(EngineeringData.COND_OVERCAST)
    'vcloudy'

    

    """
    match key:
        case self.COND_OVERCAST:
            return self._format_overcast()
        case self.COND_WIND:
            return self._format_wind()
        case self.COND_BRIGHTNESS:
            return self._format_bright()
        case self.COND_PRECIP:  
            return self._format_precip()
        case self.COND_WEATHER: 
            return self._format_weather()
        case self.COND_HUMIDITY: 
            return self._format_humidity()
        case self.COND_PRESSURE:  
            return self._format_pressure()
        case self.COND_VOLTAGE:          
            return self._format_voltage()
    return "???"    
class Interfaces

A list of access keys used when accessing data via the Boltwood.get() and Boltwood.put() methods

See Also

Boltwood, ObservingConditions, SafetyMonitor, DeviceDescriptor, EngineeringData

Examples

>>> from boltwood import Boltwood, Interfaces, ObservingConditions, DeviceDescriptor
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
(True, "-10")
>>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
(True, "")
>>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE)
(False, "Invalid Argument: property 'temperature' is read-only")
Expand source code
class Interfaces:
    """
    A list of access keys used when accessing data via the `Boltwood.get()` and `Boltwood.put()` methods
    
    See Also
    --------
    Boltwood, ObservingConditions, SafetyMonitor, DeviceDescriptor, EngineeringData

    Examples
    --------
    >>> from boltwood import Boltwood, Interfaces, ObservingConditions, DeviceDescriptor
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> bcs.get(Interfaces.OC, ObservingConditions.TEMPERATURE)
    (True, "-10")

    >>> bcs.put(Interfaces.DD, DeviceDescriptor.STA_SSID, "MyWiFiSSID")
    (True, "")

    >>> bcs.put(Interfaces.OC, ObservingConditions.TEMPERATURE)
    (False, "Invalid Argument: property 'temperature' is read-only")
    """
    
    OC = 'oc'
    """Access key for Observing Conditions serial interface"""

    SM = 'sm'
    """Access key for Safety Monitor serial interface"""

    DD = 'dd'
    """Access key for Device Descriptors serial interface"""

    EN = 'en'
    """Access key for Engineering Data serial interface"""

Class variables

var DD

Access key for Device Descriptors serial interface

var EN

Access key for Engineering Data serial interface

var OC

Access key for Observing Conditions serial interface

var SM

Access key for Safety Monitor serial interface

class ObservingConditions (str)

ObservingConditions 'all' parser and list of ObservingConditions access keys

Parameters

str : string
Results of a call to Boltwood.getOC('all')

Examples

>>> from boltwood import Boltwood, ObservingConditions
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> rc, raw_values = bcs.getOCAll()
>>> values = ObservingConditions(raw_values)
>>> print(f'{values[ObservingConditions.WIND_SPEED]}')
1.0
Expand source code
class ObservingConditions:
    ALL = 'all'
    """Access key for a space-delimited list of all parameters in ObservingCondition (read-only)"""

    AVERAGE_PERIOD = 'averageperiod' 
    """Access key for the time period over which observations will be averaged in hours (read-only)"""
    
    CLOUD_COVER = 'cloudcover' 
    """Access key for an estimate of the sky's cloud coverage in % (read-only)"""
    
    DEWPOINT = 'dewpoint' 
    """Access key for the atmospheric dew point at the observatory in degrees Celcius (read-only)"""
    
    HUMIDITY = 'humidity' 
    """Access key for the relative humidity at the observatory in % (read-only)"""
    
    PRESSURE = 'pressure' 
    """Access key for the atmospheric pressure at the observatory in hPa (read-only)"""
    
    RAIN_RATE = 'rainrate' 
    """Access key for an estimate of the rain rate at the observatory in mm/hr (read-only)"""
    
    SKY_BRIGHTNESS = 'skybrightness' 
    """Access key for an estimate of the sky brightness at the observatory in Lux (read-only)"""
    
    SKY_QUALITY = 'skyquality' 
    """Access key for the ASCOM sky quality property. Not supported, reports as 'NA' (read-only)"""
    
    SKY_TEMPERATURE = 'skytemperature' 
    """Access key for the sky temperature at the observatory in degrees Celcius (read-only)"""
    
    STAR_FWHM = 'starfwhm' 
    """Access key for the ASCOM star FWHM property. Not supported, reports as 'NA' (read-only)"""
    
    TEMPERATURE = 'temperature' 
    """Access key for the ambient temperature at the observatory in degrees Celcius (read-only)"""
    
    WIND_DIRECTION = 'winddireciton' 
    """Access key for the ASCOM Wind Direction property. Not supported, reports as 'NA' (read-only)"""
    
    WIND_GUST = 'windgust' 
    """Access key for the ASCOM Wind Gust property. Not supported, reports as 'NA' (read-only)"""
    
    WIND_SPEED = 'windspeed' 
    """Returns the wind speed at the observatory in m/s (read-only)"""

    keys_all = [
        AVERAGE_PERIOD,
        CLOUD_COVER,
        DEWPOINT,
        HUMIDITY,
        PRESSURE,
        RAIN_RATE,
        SKY_BRIGHTNESS,
        SKY_QUALITY,
        SKY_TEMPERATURE,
        STAR_FWHM,
        TEMPERATURE,
        WIND_DIRECTION,
        WIND_GUST,
        WIND_SPEED,
    ]
    """List of all keys provided by the 'all' access key, in proper API order"""

    _values = {}

    def __init__(self, str):
        """
        ObservingConditions 'all' parser and list of ObservingConditions access keys

        Parameters
        ----------
        str : string
            Results of a call to `Boltwood.getOC('all')`

        Examples
        --------
        >>> from boltwood import Boltwood, ObservingConditions
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCAll()
        >>> values = ObservingConditions(raw_values)
        >>> print(f'{values[ObservingConditions.WIND_SPEED]}')
        1.0
        """
        self._parse(str)

    def _parse(self, str):
        arr = str.split(' ')
        for i in range(0, len(self.keys_all)):
            self._values[self.keys_all[i]] = arr[i]

    def __getitem__(self, key):
        """
        Array accessor for parsed values, indexed by access keys listed above

        Parameters
        ----------
        key : string
            ObservingConditions access key to retrieve

        Returns
        -------
        value : string
            Stored value for provided access key 
        """
        return self._values[key]

Class variables

var ALL

Access key for a space-delimited list of all parameters in ObservingCondition (read-only)

var AVERAGE_PERIOD

Access key for the time period over which observations will be averaged in hours (read-only)

var CLOUD_COVER

Access key for an estimate of the sky's cloud coverage in % (read-only)

var DEWPOINT

Access key for the atmospheric dew point at the observatory in degrees Celcius (read-only)

var HUMIDITY

Access key for the relative humidity at the observatory in % (read-only)

var PRESSURE

Access key for the atmospheric pressure at the observatory in hPa (read-only)

var RAIN_RATE

Access key for an estimate of the rain rate at the observatory in mm/hr (read-only)

var SKY_BRIGHTNESS

Access key for an estimate of the sky brightness at the observatory in Lux (read-only)

var SKY_QUALITY

Access key for the ASCOM sky quality property. Not supported, reports as 'NA' (read-only)

var SKY_TEMPERATURE

Access key for the sky temperature at the observatory in degrees Celcius (read-only)

var STAR_FWHM

Access key for the ASCOM star FWHM property. Not supported, reports as 'NA' (read-only)

var TEMPERATURE

Access key for the ambient temperature at the observatory in degrees Celcius (read-only)

var WIND_DIRECTION

Access key for the ASCOM Wind Direction property. Not supported, reports as 'NA' (read-only)

var WIND_GUST

Access key for the ASCOM Wind Gust property. Not supported, reports as 'NA' (read-only)

var WIND_SPEED

Returns the wind speed at the observatory in m/s (read-only)

var keys_all

List of all keys provided by the 'all' access key, in proper API order

class SafetyMonitor

A list of access keys used when accessing data via Boltwood.getSM()

Examples

>>> from boltwood import Boltwood, SafetyMonitor
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> bcs.getSM(SafetyMonitor.IS_SAFE)
(True, "1")
Expand source code
class SafetyMonitor:
    """
    A list of access keys used when accessing data via `Boltwood.getSM()`

    Examples
    --------

    >>> from boltwood import Boltwood, SafetyMonitor
    >>> bcs = Boltwood("COM1")
    >>> bcs.open()
    >>> bcs.getSM(SafetyMonitor.IS_SAFE)
    (True, "1")
    """

    IS_SAFE = "isSafe"
    """
    Access key for the ASCOM Safety Monitor 'isSafe' property (read-only)
    
    1 is safe, 0 is unsafe
    """

Class variables

var IS_SAFE

Access key for the ASCOM Safety Monitor 'isSafe' property (read-only)

1 is safe, 0 is unsafe

class Thresholds (str='')

Threshold/Safety Trigger decoder class

Parameters

str : string
The value result of a call to Boltwood.getOCThresholds()

See Also

Boltwood.getOCThresholds()

Examples

>>> from boltwood import Boltwood, Thresholds
>>> bcs = Boltwood("COM1")
>>> bcs.open()
>>> rc, raw_values = bcs.getOCThresholds()
>>> values = Thresholds(raw_values)
>>> values.threshValue[Thresholds.CLEAR_CLOUDY] # Threshold value
-10
>>> values.roofTrig[Thresholds.CLEAR_CLOUDY] # Active as safety trigger
0
>>> values[Thresholds.CLEAR_CLOUDY] # Tuple of threshold value & safety trigger
("-10", 0)
Expand source code
class Thresholds:
    CLEAR_CLOUDY     = 'clearCloudy'
    """Threshold/Safety Trigger accessor key for reporting cloudy when (sky temp - ambient temp) > threshold in degrees Celcius"""

    CLOUDY_VCLOUDY   = 'cloudyVeryCloudy'
    """Threshold/Safety Trigger accessor key for reporting vcloudy when (sky temp - ambient temp) > threshold in degrees Celcius"""

    CALM_WINDY       = 'calmWindy'
    """Threshold/Safety Trigger accessor key for reporting windy when windspeed > threshold in km/h"""

    WINDY_VWINDY     = 'windyVeryWindy'
    """Threshold/Safety Trigger accessor key for reporting vwindy when windspeed > threshold in km/h"""

    DARK_LIGHT       = 'darkLight'
    """Threshold/Safety Trigger accessor key for reporting light when brightness > threshold in %"""

    LIGHT_VLIGHT     = 'lightVeryLight'
    """Threshold/Safety Trigger accessor key for reporting vlight when brightness > threshold in %"""

    RAIN_SENSITIVITY = 'rainSensitivity'
    """Threshold/Safety Trigger accessor key for reporting rain hit when rain sensor duty cycle > threshold in microseconds"""

    RAIN_DPM         = 'rainDpmThreshold'
    """Threshold/Safety Trigger accessor key for reporting raining/snowing when dpm > threshold in drops/minute"""

    HUMIDITY_LOW     = 'humidityLow'
    """Threshold/Safety Trigger accessor key for reporting low humidity when humidity < threshold in %"""

    HUMIDITY_HIGH    = 'humidityHigh'
    """Threshold/Safety Trigger accessor key for reporting high humidity when humidity > threshold in %"""

    PRESSURE_LOW     = 'pressureLow'
    """Threshold/Safety Trigger accessor key for reporting low pressure when pressure < threshold in mBar"""

    PRESSURE_HIGH    = 'pressureHigh'
    """Threshold/Safety Trigger accessor key for reporting high pressure when pressure > threshold in mBar"""

    VOLTAGE_LOW      = 'voltageLow'
    """Threshold/Safety Trigger accessor key for reporting low voltage when voltage < threshold in V"""

    VOLTAGE_HIGH     = 'voltageHigh'
    """Threshold/Safety Trigger accessor key for reporting high voltage when voltage > threshold in V"""

    keys = [
        CLEAR_CLOUDY,     
        CLOUDY_VCLOUDY,      
        CALM_WINDY,       
        WINDY_VWINDY,        
        DARK_LIGHT,       
        LIGHT_VLIGHT,    
        RAIN_SENSITIVITY,
        RAIN_DPM,       
        HUMIDITY_LOW,      
        HUMIDITY_HIGH, 
        PRESSURE_LOW,      
        PRESSURE_HIGH,      
        VOLTAGE_LOW,       
        VOLTAGE_HIGH
    ]
    """List of Threshold/Safety Trigger accessor keys"""

    threshValue = {}
    """List of Threshold values indexed by Threshold/Safety Trigger accessor keys"""

    roofTrig = {}
    """List of Safety Trigger values indexed by Threshold/Safety Trigger accessor keys"""
    
    def __init__(self, str=''):
        """
        Threshold/Safety Trigger decoder class 

        Parameters
        ----------
        str : string
            The value result of a call to `boltwood.Boltwood.getOCThresholds()`

        See Also
        --------
        Boltwood.getOCThresholds()

        Examples
        --------
        >>> from boltwood import Boltwood, Thresholds
        >>> bcs = Boltwood("COM1")
        >>> bcs.open()
        >>> rc, raw_values = bcs.getOCThresholds()
        >>> values = Thresholds(raw_values)
        >>> values.threshValue[Thresholds.CLEAR_CLOUDY] # Threshold value
        -10
        >>> values.roofTrig[Thresholds.CLEAR_CLOUDY] # Active as safety trigger
        0
        >>> values[Thresholds.CLEAR_CLOUDY] # Tuple of threshold value & safety trigger
        ("-10", 0)
        """
        self._parse(str)

    def _parse(self, str):
        if not str: return
        arr = str.split(" ")
        for i in range(0, len(self.keys)):
            self.threshValue[self.keys[i]] = arr[i]
            self.roofTrig[self.keys[i]] = arr[i + len(self.keys)]
                               
    def to_string(self):
        """
        Encodes values stored in this class instance into a string for transmission via boltwood.Boltwood.setOCThresholds()

        Examples
        --------
        >>> from boltwood import Boltwood, Thresholds
        >>> bcs = Boltwood("COM1") 
        >>> bcs.open()
        >>> # Fetch existing thresholds first
        >>> rc, raw_thresholds = bcs.getOCThresholds() 
        >>> # Parse the result
        >>> thresholds = Thresholds(raw_thresholds) 
        >>> # Add Thresholds.CLEAR_CLOUDY to list of active safety triggers
        >>> thresholds.roofTrig[Thresholds.CLEAR_CLOUDY] = 1 
        >>> # Send updated threshold structure to Boltwood, expect: (True, "")
        >>> bcs.setOCThresholds(thresholds.to_string())
        (True, "")

        Returns
        -------
        encoded_str : string
            The Threshold/Safety Trigger values stored in proper order for transmission to a BCSIII device over a serial connection
        """
        tmp = []
        for key in self.keys:
            tmp.append("{}".format(self.threshValue[key]))
        for key in self.keys:
            tmp.append("{}".format(self.roofTrig[key]))
        return ' '.join(tmp)
    
    def __getitem__(self, key):
        """
        Accessor for Thresholds/Safety Trigger entries

        Looks up a stored Threshold/Safety Trigger value by access key and returns it as a tuple

        Parameters
        ----------
        key : string
            Thresholds/Safety Trigger ccess key, or raw string representing the threshold to be accessed

        Returns
        -------
        threshold : string
            string-encoded value of the Thresholds

        safety_trigger : string
            string-encoded value of whether the Safety Trigger is active for the provided key ('1' for active, '0' for inactive)    
        """
        return (self.threshValue[key], self.roofTrig[key])

Class variables

var CALM_WINDY

Threshold/Safety Trigger accessor key for reporting windy when windspeed > threshold in km/h

var CLEAR_CLOUDY

Threshold/Safety Trigger accessor key for reporting cloudy when (sky temp - ambient temp) > threshold in degrees Celcius

var CLOUDY_VCLOUDY

Threshold/Safety Trigger accessor key for reporting vcloudy when (sky temp - ambient temp) > threshold in degrees Celcius

var DARK_LIGHT

Threshold/Safety Trigger accessor key for reporting light when brightness > threshold in %

var HUMIDITY_HIGH

Threshold/Safety Trigger accessor key for reporting high humidity when humidity > threshold in %

var HUMIDITY_LOW

Threshold/Safety Trigger accessor key for reporting low humidity when humidity < threshold in %

var LIGHT_VLIGHT

Threshold/Safety Trigger accessor key for reporting vlight when brightness > threshold in %

var PRESSURE_HIGH

Threshold/Safety Trigger accessor key for reporting high pressure when pressure > threshold in mBar

var PRESSURE_LOW

Threshold/Safety Trigger accessor key for reporting low pressure when pressure < threshold in mBar

var RAIN_DPM

Threshold/Safety Trigger accessor key for reporting raining/snowing when dpm > threshold in drops/minute

var RAIN_SENSITIVITY

Threshold/Safety Trigger accessor key for reporting rain hit when rain sensor duty cycle > threshold in microseconds

var VOLTAGE_HIGH

Threshold/Safety Trigger accessor key for reporting high voltage when voltage > threshold in V

var VOLTAGE_LOW

Threshold/Safety Trigger accessor key for reporting low voltage when voltage < threshold in V

var WINDY_VWINDY

Threshold/Safety Trigger accessor key for reporting vwindy when windspeed > threshold in km/h

var keys

List of Threshold/Safety Trigger accessor keys

var roofTrig

List of Safety Trigger values indexed by Threshold/Safety Trigger accessor keys

var threshValue

List of Threshold values indexed by Threshold/Safety Trigger accessor keys

Methods

def to_string(self)

Encodes values stored in this class instance into a string for transmission via boltwood.Boltwood.setOCThresholds()

Examples

>>> from boltwood import Boltwood, Thresholds
>>> bcs = Boltwood("COM1") 
>>> bcs.open()
>>> # Fetch existing thresholds first
>>> rc, raw_thresholds = bcs.getOCThresholds() 
>>> # Parse the result
>>> thresholds = Thresholds(raw_thresholds) 
>>> # Add Thresholds.CLEAR_CLOUDY to list of active safety triggers
>>> thresholds.roofTrig[Thresholds.CLEAR_CLOUDY] = 1 
>>> # Send updated threshold structure to Boltwood, expect: (True, "")
>>> bcs.setOCThresholds(thresholds.to_string())
(True, "")

Returns

encoded_str : string
The Threshold/Safety Trigger values stored in proper order for transmission to a BCSIII device over a serial connection
Expand source code
def to_string(self):
    """
    Encodes values stored in this class instance into a string for transmission via boltwood.Boltwood.setOCThresholds()

    Examples
    --------
    >>> from boltwood import Boltwood, Thresholds
    >>> bcs = Boltwood("COM1") 
    >>> bcs.open()
    >>> # Fetch existing thresholds first
    >>> rc, raw_thresholds = bcs.getOCThresholds() 
    >>> # Parse the result
    >>> thresholds = Thresholds(raw_thresholds) 
    >>> # Add Thresholds.CLEAR_CLOUDY to list of active safety triggers
    >>> thresholds.roofTrig[Thresholds.CLEAR_CLOUDY] = 1 
    >>> # Send updated threshold structure to Boltwood, expect: (True, "")
    >>> bcs.setOCThresholds(thresholds.to_string())
    (True, "")

    Returns
    -------
    encoded_str : string
        The Threshold/Safety Trigger values stored in proper order for transmission to a BCSIII device over a serial connection
    """
    tmp = []
    for key in self.keys:
        tmp.append("{}".format(self.threshValue[key]))
    for key in self.keys:
        tmp.append("{}".format(self.roofTrig[key]))
    return ' '.join(tmp)