Apogee USB Sensors and Linux

Apogee USB sensors can be connected directly to a computer (Windows or macOS X) for taking spot measurements or graphing and datalogging real-time PPFD using the included software. Apogee currently does not have software for our sensors with Linux systems; however, while we do not design custom programs, we can offer a set of commands, sample code, and comments from the developer of ApogeeConnect that should help provide a good foundation for getting your own program started using Apogee USB sensors (SP-420, SQ-420, SQ-520, etc.). This information is copied below:

Command

Transmitted Bytes

Returns

GET_VOLT 55 21 5 [cmd + four byte floating point number]
READ_CALIBRATION 83 21

9 [cmd + four byte float multiplier + four byte float offset]   

SET_CALIBRATION 84 XX XX XX XX YY YY YY YY 21 (X = 4 byte floating point multiplier, Y = 4 byte floating point offset) 9 [same as above]
READ_SERIAL_NUM 87 21

5 [cmd + four byte floating point number] 

GET_LOGGING_INTERVAL f1 21

9 [cmd + logging interval + sample interval] all floats

GET_LOGGING_COUNT f3 21

5 [cmd + number of entries] four byte integer

GET_LOGGED_ENTRY f2 XX XX XX XX 21 (X = 4 byte long integer)

5 [cmd + four byte floating point number]

SET_LOGGING_INTERVAL f0 XX XX XX XX YY YY YY YY 21 (X = 4 byte floating point logging interval, Y = 4 byte floating point sampling interval

8 [cmd + received logging interval + 3 dummy bytes]

ERASE_LOGGED_DATA f4 21 4 [cmd + 3 dummy bytes]

There are many online resources on how to convert hexadecimal to floating point numbers. Here's a link with a few suggestions from other programmers using c# https://protect-eu.mimecast.com/s/pokJCZzGOF7xBKtNSk8F?domain=url9639.waterbearenergy.com.

The response from Apogee USB sensors is always sent in little endian format meaning the least significant byte is sent first.

Below is a super basic python class similar to the one used in the ApogeeConnect software. In the code, you can see examples of how to read in calibration data and how to use it to convert the voltage to usable data. This code is written for quantum sensors but will work the same way for pyranometers and UV USB sensors. The developer of ApogeeConnect has found python to be very easy to use with these sensors. If using python, you will need to install the Pyserial library if you do not already have it. This code is meant to work on Windows using python 2.7. Some modifications may be necessary for other operating systems and/or python 3.

*Please note: This code was thrown together without testing, so it may or may not need some debugging.

from serial import Serial

from time import sleep

import struct

GET_VOLT = '\x55!'

READ_CALIBRATION = '\x83!'

SET_CALIBRATION = '\x84%s%s!'

READ_SERIAL_NUM = '\x87!'

GET_LOGGING_COUNT = '\xf3!'

GET_LOGGED_ENTRY = '\xf2%s!'

ERASE_LOGGED_DATA = '\xf4!'

class Quantum(object):

                def __init__(self):

                                """Initializes class variables, and attempts to connect to device"""

                                self.quantum = None

                                self.offset = 0.0

                                self.multiplier = 0.0

                                self.connect_to_device()

 

                def connect_to_device(self):

                                """This function creates a Serial connection with the defined comport

                                and attempts to read the calibration values"""

                                port = 'COM1' # you'll have to check your device manager and put the actual com port here

                                self.quantum = Serial(port, 115200, timeout=0.5)

                                try:

                                                self.quantum.write(READ_CALIBRATION)

                                                multiplier = self.quantum.read(5)[1:]

                                                offset = self.quantum.read(4)

                                                self.multiplier = struct.unpack('<f', multiplier)[0]

                                                self.offset = struct.unpack('<f', offset)[0]

                                except (IOError, struct.Error), data:

                                                print data

                                                self.quantum = None

 

                def get_micromoles(self):

                                """This function converts the voltage to micromoles"""

                                voltage = self.read_voltage()

                                if voltage == 9999:

                                                # you could raise some sort of Exception here if you wanted to

                                                return

                                # this next line converts volts to micromoles

                                micromoles = (voltage - self.offset) * self.multiplier * 1000

                                if micromoles < 0:

                                                micromoles = 0

                                return micromoles

 

                def read_voltage(self):

                                """This function averages 5 readings over 1 second and returns

                                the result."""

                                if self.quantum == None:

                                                try:

                                                                self.connect_to_device()

                                                except IOError:

                                                                # you can raise some sort of exception here if you need to

                                                                return

                                # store the responses to average

                                response_list = []

                                # change to average more or less samples over the given time period

                                number_to_average = 5

                                # change to shorten or extend the time duration for each measurement

                                # be sure to leave as floating point to avoid truncation

                                number_of_seconds = 1.0

                                for i in range(number_to_average):

                                                try:

                                                                self.quantum.write(GET_VOLT)

                                                                response = self.quantum.read(5)[1:]

                                                except IOError, data:

                                                                print data

                                                                # dummy value to know something went wrong. could raise an

                                                                # exception here alternatively

                                                                return 9999

                                                else:

                                                                if not response:

                                                                                continue

                                                                # if the response is not 4 bytes long, this line will raise

                                                                # an exception

                                                                voltage = struct.unpack('<f', response)[0]

                                                                response_list.append(voltage)

                                                                sleep(number_of_seconds/number_to_average)

                                if response_list:

                                                return sum(response_list)/len(response_list)

                                return 0.0

 

     **Please note: Apogee does not provide technical support for custom programs.