#!/usr/bin/python # coding=utf-8 import RPi.GPIO as GPIO import time import spidev import ctypes import subprocess import shlex # This file is part of the PiXtend(R) Project. # # For more information about PiXtend(R) and this program, # see or # # Copyright (C) 2018 Robin Turner # Qube Solutions UG (haftungsbeschränkt), Arbachtalstr. 6 # 72800 Eningen, Germany # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . __author__ = "Robin Turner" __version__ = "0.1.1" # """ Use the UINT8 cType to create a one (1) byte length variable, short name c_uint8 """ c_uint8 = ctypes.c_uint8 """ Use the UINT16 cType to create a two (2) byte length variable, short version c_uint16 """ c_uint16 = ctypes.c_uint16 """ Use the FLOAT type from cTypes and make a short version c_float """ c_float = ctypes.c_float # # class FlagsBits(ctypes.LittleEndianStructure): """ This class has a field with named bits within one single byte, they will be used later to control/change individual bits in one single byte. :type bit0 : c_uint8 :type bit1 : c_uint8 :type bit2 : c_uint8 :type bit3 : c_uint8 :type bit4 : c_uint8 :type bit5 : c_uint8 :type bit6 : c_uint8 :type bit7 : c_uint8 """ def __init__(self, *args, **kwargs): super(FlagsBits, self).__init__(*args, **kwargs) self.bit0 = 0 self.bit1 = 0 self.bit2 = 0 self.bit3 = 0 self.bit4 = 0 self.bit5 = 0 self.bit6 = 0 self.bit7 = 0 _fields_ = [ ("bit0", c_uint8, 1), # asByte & 1 ("bit1", c_uint8, 1), # asByte & 2 ("bit2", c_uint8, 1), # asByte & 4 ("bit3", c_uint8, 1), # asByte & 8 ("bit4", c_uint8, 1), # asByte & 16 ("bit5", c_uint8, 1), # asByte & 32 ("bit6", c_uint8, 1), # asByte & 64 ("bit7", c_uint8, 1) # asByte & 128 ] class FlagsBits16(ctypes.LittleEndianStructure): """ This class has a field with named bits within one 2 bytes structure (c_uint16 type), they will be used later to control/change individual bits in two bytes. :type bit0 : ctypes.c_ushort :type bit1 : ctypes.c_ushort :type bit2 : ctypes.c_ushort :type bit3 : ctypes.c_ushort :type bit4 : ctypes.c_ushort :type bit5 : ctypes.c_ushort :type bit6 : ctypes.c_ushort :type bit7 : ctypes.c_ushort :type bit8 : ctypes.c_ushort :type bit9 : ctypes.c_ushort :type bit10 : ctypes.c_ushort :type bit11 : ctypes.c_ushort :type bit12 : ctypes.c_ushort :type bit13 : ctypes.c_ushort :type bit14 : ctypes.c_ushort :type bit15 : ctypes.c_ushort """ def __init__(self, *args, **kwargs): super(FlagsBits16, self).__init__(*args, **kwargs) self.bit0 = 0 self.bit1 = 0 self.bit2 = 0 self.bit3 = 0 self.bit4 = 0 self.bit5 = 0 self.bit6 = 0 self.bit7 = 0 self.bit8 = 0 self.bit9 = 0 self.bit10 = 0 self.bit11 = 0 self.bit12 = 0 self.bit13 = 0 self.bit14 = 0 self.bit15 = 0 _fields_ = [ ("bit0", c_uint16, 1), # asByte & 1 ("bit1", c_uint16, 1), # asByte & 2 ("bit2", c_uint16, 1), # asByte & 4 ("bit3", c_uint16, 1), # asByte & 8 ("bit4", c_uint16, 1), # asByte & 16 ("bit5", c_uint16, 1), # asByte & 32 ("bit6", c_uint16, 1), # asByte & 64 ("bit7", c_uint16, 1), # asByte & 128 ("bit8", c_uint16, 1), # asByte & 256 ("bit9", c_uint16, 1), # asByte & 512 ("bit10", c_uint16, 1), # asByte & 1024 ("bit11", c_uint16, 1), # asByte & 2048 ("bit12", c_uint16, 1), # asByte & 4096 ("bit13", c_uint16, 1), # asByte & 8192 ("bit14", c_uint16, 1), # asByte & 16384 ("bit15", c_uint16, 1) # asByte & 32768 ] class Flags(ctypes.Union): """ A class using the ctypes Union to combine the 8 bits from the FlagsBits class (field) and one byte (type c_uint8) into one memory space overlaying each other. This way each single bit within a byte can be accessed directly by its individual name. Using the asBytes field element allows to set all 8 bits at once. :type b : FlagsBits :type asByte : c_uint8 """ def __init__(self, *args, **kwargs): super(Flags, self).__init__(*args, **kwargs) self.b = FlagsBits() self.asByte = 0 _fields_ = [ ("b", FlagsBits), ("asByte", c_uint8) ] class AnalogValueBytes(ctypes.Structure): """ This is a structure for two (2) single bytes to split up a 16 bit / 2 bytes data type. This class can be used in a Union. :type byte0 : c_uint8 :type byte1 : c_uint8 """ def __init__(self, *args, **kwargs): super(AnalogValueBytes, self).__init__(*args, **kwargs) self.byte0 = c_uint8(0) self.byte1 = c_uint8(0) _fields_ = [ ("byte0", c_uint8), ("byte1", c_uint8) ] class AnalogValue(ctypes.Union): """ Union from ctypes to combine the AnalogValueBytes structure with a C_UINT16 type to make a double byte memory area. This memory can then be accessed via the C_UINT16 (asUint16) field or via 2 individual named bytes from the structure AnalogValueBytes. :type bytes : AnalogValueBytes :type asUint16 : c_uint16 """ def __init__(self, *args, **kwargs): super(AnalogValue, self).__init__(*args, **kwargs) self.bytes = AnalogValueBytes() self.asUint16 = c_uint16(0) _fields_ = [ ("bytes", AnalogValueBytes), ("asUint16", c_uint16) ] class Flags16(ctypes.Union): """ Union from ctypes to combine a 16 bits structure with a C_UINT16 type to make a two byte memory space for the SPI data which will be sent to the DAC via SPI Master 0 and Chip Select 1. :type bits : FlagsBits16 :type asUint16 : c_uint16 """ def __init__(self, *args, **kwargs): super(Flags16, self).__init__(*args, **kwargs) self.bits = FlagsBits16() self.asUint16 = c_uint16(0) _fields_ = [ ("bits", FlagsBits16), ("asUint16", c_uint16) ] class UcVersionBytes(ctypes.Structure): """ This is a structure for two (2) single bytes to store the firmware version and the board version reported by the microcontroller on the PiXtend board. Example: UC_VERSIONH = 13 means board version is 1.3.x UC_VERSIONL = 2 means firmware version 2.x is installed on the microcontroller :type UC_VERSIONL : c_uint8 :type UC_VERSIONH : c_uint8 """ def __init__(self, *args, **kwargs): super(UcVersionBytes, self).__init__(*args, **kwargs) self.UC_VERSIONL = c_uint8(0) self.UC_VERSIONH = c_uint8(0) _fields_ = [ ("UC_VERSIONL", c_uint8), ("UC_VERSIONH", c_uint8) ] # class Pixtend(object): """ The PiXtend class derived from Python object can be used to control and manipulate states of inputs and outputs (analog and digital) in the microcontroller on the PiXtend board via the Raspberry Pi's SPI bus. Import the pixtend file: from pixtendlib import Pixtend Creating an instance: p = Pixtend() Activate the SPI bus: p.open() Read relay 0 state: mystate = p.relay0 Set relay 1 to on: p.relay1 = p.ON """ # # Global class defines PIXTEND_MAX_RELAYS = 4 PIXTEND_MC_RESET_PIN = 23 PIXTEND_SPI_ENABLE_PIN = 24 PIXTEND_SERIAL_PIN = 18 PIXTEND_SPI_NOT_FOUND = -1 PIXTEND_SPI_NULL_BYTE = 0b00000000 PIXTEND_SPI_HANDSHAKE = 0b10101010 # -------------------------------------- # 4-Byte Command length # -------------------------------------- PIXTEND_SPI_SET_DOUT = 0b00000001 PIXTEND_SPI_GET_DIN = 0b00000010 PIXTEND_SPI_SET_RELAY = 0b00000111 PIXTEND_SPI_SET_GPIO = 0b00001000 PIXTEND_SPI_GET_GPIO = 0b00001001 PIXTEND_SPI_GET_DOUT = 0b00010010 PIXTEND_SPI_GET_RELAY = 0b00010011 PIXTEND_SPI_SET_SERVO0 = 0b10000000 PIXTEND_SPI_SET_SERVO1 = 0b10000001 PIXTEND_SPI_SET_GPIO_CTRL = 0b10000101 PIXTEND_SPI_SET_UC_CTRL = 0b10000110 PIXTEND_SPI_SET_RASPSTAT = 0b10001000 PIXTEND_SPI_GET_UC_STAT = 0b10001010 # -------------------------------------- # 5-Byte Command length # -------------------------------------- PIXTEND_SPI_GET_AIN0 = 0b00000011 PIXTEND_SPI_GET_AIN1 = 0b00000100 PIXTEND_SPI_GET_AIN2 = 0b00000101 PIXTEND_SPI_GET_AIN3 = 0b00000110 PIXTEND_SPI_GET_TEMP0 = 0b00001010 PIXTEND_SPI_GET_TEMP1 = 0b00001011 PIXTEND_SPI_GET_TEMP2 = 0b00001100 PIXTEND_SPI_GET_TEMP3 = 0b00001101 PIXTEND_SPI_GET_HUM0 = 0b00001110 PIXTEND_SPI_GET_HUM1 = 0b00001111 PIXTEND_SPI_GET_HUM2 = 0b00010000 PIXTEND_SPI_GET_HUM3 = 0b00010001 PIXTEND_SPI_SET_PWM0 = 0b10000010 PIXTEND_SPI_SET_PWM1 = 0b10000011 PIXTEND_SPI_SET_AI_CTRL = 0b10000111 PIXTEND_SPI_GET_UC_VER = 0b10001001 # -------------------------------------- # 6-Byte Command length # -------------------------------------- PIXTEND_SPI_SET_PWM_CTRL = 0b10000100 # -------------------------------------- # Auto Mode - 34 bytes Command length # -------------------------------------- PIXTEND_SPI_AUTO_MODE = 0b11100111 # -------------------------------------- # Define types, states and selections # -------------------------------------- ON = 1 OFF = 0 RS232 = False RS485 = True DAC_A = 0 DAC_B = 1 SERVO_MODE = 0 PWM_MODE = 1 GPIO_OUTPUT = 1 GPIO_INPUT = 0 JUMPER_5V = 0 JUMPER_10V = 1 BIT_0 = 0 BIT_1 = 1 BIT_2 = 2 BIT_3 = 3 BIT_4 = 4 BIT_5 = 5 BIT_6 = 6 BIT_7 = 7 BIT_8 = 8 BIT_9 = 9 BIT_10 = 10 BIT_11 = 11 BIT_12 = 12 BIT_13 = 13 BIT_14 = 14 BIT_15 = 15 # def __init__(self): """ Constructor of the Pixtend class. Create all objects and variables needed, set defaults for the RPi SPI bus, the GPIOs and activate the BCM GPIO layout on the Raspberry Pi. The GPIO 24 needs to be an output and set to 'on' to enable the communication with the microcontroller on the PiXtend board via SPI Master 0 and Chip Select 0. """ # Default SPI frequency is 100kHz self.__spi_speed = 100000 # The microcontroller is on the SPI Master 0 with CS 0 self.__spi_channel = 0 self.__spi_cs = 0 self.__spi = None self.__spi_dac = None # Initialize variables self.__use_fahrenheit = False self.__is_automatic_mode_active = False self.__is_spi_open = False self.__is_spi_dac_open = False self.__max_relay_bits = self.PIXTEND_MAX_RELAYS self.__relays_states = 0 self.__digital_inputs_states = 0 self.__digital_outputs_states = 0 self.__gpio_states_manual = 0 self.__gpio_states_auto_in = 0 self.__gpio_states_auto_out = 0 self.__gpio_ctrl = 0 self.__servo0_value = c_uint8(0) self.__servo1_value = c_uint8(0) self.__pwm0_value = AnalogValue() self.__pwm1_value = AnalogValue() self.__pwm_ctrl0 = Flags() self.__pwm_ctrl1 = Flags() self.__pwm_ctrl2 = Flags() self.__pwm_period = AnalogValue() self.__analog_dac_value = Flags16() self.__uc_version = UcVersionBytes() self.__uc_status = c_uint8(0) self.__uc_ctrl = Flags() self.__ai_ctrl0 = Flags() self.__ai_ctrl1 = Flags() self.__pi_status = Flags() self.__analog_value = AnalogValue() self.__ai0_jumper_setting_10_volts = 0 self.__ai1_jumper_setting_10_volts = 0 self.__ai0_raw_value = AnalogValue() self.__ai1_raw_value = AnalogValue() self.__ai2_raw_value = AnalogValue() self.__ai3_raw_value = AnalogValue() self.__temp0_raw_value = AnalogValue() self.__temp1_raw_value = AnalogValue() self.__temp2_raw_value = AnalogValue() self.__temp3_raw_value = AnalogValue() self.__humid0_raw_value = AnalogValue() self.__humid1_raw_value = AnalogValue() self.__humid2_raw_value = AnalogValue() self.__humid3_raw_value = AnalogValue() self.__ai0_value = c_float(0.0) self.__ai1_value = c_float(0.0) self.__ai2_value = c_float(0.0) self.__ai3_value = c_float(0.0) self.__temp0_value = c_float(0.0) self.__temp1_value = c_float(0.0) self.__temp2_value = c_float(0.0) self.__temp3_value = c_float(0.0) self.__humid0_value = c_float(0.0) self.__humid1_value = c_float(0.0) self.__humid2_value = c_float(0.0) self.__humid3_value = c_float(0.0) self.__analog0_dac_value = Flags16() self.__analog1_dac_value = Flags16() # Turn RPi GPIO warnings off in case GPIOs are still/already in use GPIO.setwarnings(False) # Change layout to BCM GPIO.setmode(GPIO.BCM) # Set SPI Enable pin to output GPIO.setup(self.PIXTEND_SPI_ENABLE_PIN, GPIO.OUT) GPIO.setup(self.PIXTEND_MC_RESET_PIN, GPIO.OUT) GPIO.setup(self.PIXTEND_SERIAL_PIN, GPIO.OUT) # Activate SPI Enable, allow communication GPIO.output(self.PIXTEND_SPI_ENABLE_PIN, True) # Turn microcontroller reset pin off GPIO.output(self.PIXTEND_MC_RESET_PIN, False) # Set serial mode to RS232 GPIO.output(self.PIXTEND_SERIAL_PIN, False) @staticmethod def __dump(obj): for attr in dir(obj): if hasattr(obj, attr): print("obj.%s = %s" % (attr, getattr(obj, attr))) def __del__(self): """ Destructor of the Pixtend class. Delete all objects, clean up GPIOs and close the SPI bus when the Pixtend instance is destroyed. """ self.__use_fahrenheit = False self.__is_automatic_mode_active = False try: del self.__relays_states except: pass try: del self.__digital_inputs_states except: pass try: del self.__digital_outputs_states except: pass try: del self.__analog_value except: pass try: del self.__gpio_states_manual except: pass try: del self.__gpio_states_auto_in except: pass try: del self.__gpio_states_auto_out except: pass try: del self.__servo0_value except: pass try: del self.__servo1_value except: pass try: del self.__pwm0_value except: pass try: del self.__pwm1_value except: pass try: del self.__pwm_ctrl0 except: pass try: del self.__pwm_ctrl1 except: pass try: del self.__pwm_ctrl2 except: pass try: del self.__pwm_period except: pass try: del self.__analog_dac_value except: pass try: del self.__uc_ctrl except: pass try: del self.__ai_ctrl0 except: pass try: del self.__ai_ctrl1 except: pass try: del self.__pi_status except: pass try: del self.__ai0_jumper_setting_10_volts except: pass try: del self.__ai1_jumper_setting_10_volts except: pass try: del self.__ai0_raw_value except: pass try: del self.__ai1_raw_value except: pass try: del self.__ai2_raw_value except: pass try: del self.__ai3_raw_value except: pass try: del self.__temp0_raw_value except: pass try: del self.__temp1_raw_value except: pass try: del self.__temp2_raw_value except: pass try: del self.__temp3_raw_value except: pass try: del self.__humid0_raw_value except: pass try: del self.__humid1_raw_value except: pass try: del self.__humid2_raw_value except: pass try: del self.__humid3_raw_value except: pass try: del self.__ai0_value except: pass try: del self.__ai1_value except: pass try: del self.__ai2_value except: pass try: del self.__ai3_value except: pass try: del self.__temp0_value except: pass try: del self.__temp1_value except: pass try: del self.__temp2_value except: pass try: del self.__temp3_value except: pass try: del self.__humid0_value except: pass try: del self.__humid1_value except: pass try: del self.__humid2_value except: pass try: del self.__humid3_value except: pass try: del self.__analog0_dac_value except: pass try: del self.__analog1_dac_value except: pass try: GPIO.cleanup() except: pass if self.__is_spi_open: try: self.__spi.close() except: pass self.__spi = None self.__is_spi_open = False if self.__is_spi_dac_open: try: self.__spi_dac.close() except: pass self.__spi_dac = None self.__is_spi_dac_open = False try: del self.__spi except: pass try: del self.__spi_dac except: pass try: del self.__is_spi_open except: pass try: del self.__is_spi_dac_open except: pass def __transfer_spi_data(self, command, value0=0b00000000, value1=0b00000000, value2=0b00000000, auto_data=None): """ Transfer data to microcontroller all in one block, data needs to be passed as byte array, the return value is also a byte array / list of the same byte count as was sent to the microcontroller. :param int command: Command for the microcontroller to execute, must be of one of the PIXTEND_SPI_xxx constants :param int value0: First byte value needed for 4 byte commands, optional depending on command :param int value1: Second byte value needed for 5 byte commands, optional depending on command :param int value2: Third byte value needed for 6 byte commands, optional depending on command :param auto_data: Array of bytes to send to the microcontroller in Auto Mode :type auto_data: int[] or None :return: Array of bytes received from the microcontroller on the PiXtend board :rtype: int[] :raises Exception: If command does not have one of the allowed PIXTEND_SPI_xxx constants values :raises IOError: If SPI bus was not initialized (opened) before use """ # auto_data is mutable, None is used as default, if no parameter gets passed, it is inited as an array. if auto_data is None: auto_data = [0] # if (command == self.PIXTEND_SPI_SET_DOUT) or (command == self.PIXTEND_SPI_GET_DIN) or ( command == self.PIXTEND_SPI_SET_RELAY) or (command == self.PIXTEND_SPI_SET_GPIO) or ( command == self.PIXTEND_SPI_GET_GPIO) or (command == self.PIXTEND_SPI_GET_DOUT) or ( command == self.PIXTEND_SPI_GET_RELAY) or (command == self.PIXTEND_SPI_SET_SERVO0) or ( command == self.PIXTEND_SPI_SET_SERVO1) or (command == self.PIXTEND_SPI_SET_GPIO_CTRL) or ( command == self.PIXTEND_SPI_SET_UC_CTRL) or (command == self.PIXTEND_SPI_SET_RASPSTAT) or ( command == self.PIXTEND_SPI_GET_UC_STAT): to_send = [self.PIXTEND_SPI_HANDSHAKE, command, value0, self.PIXTEND_SPI_HANDSHAKE] elif (command == self.PIXTEND_SPI_GET_AIN0) or (command == self.PIXTEND_SPI_GET_AIN1) or ( command == self.PIXTEND_SPI_GET_AIN2) or (command == self.PIXTEND_SPI_GET_AIN3) or ( command == self.PIXTEND_SPI_GET_TEMP0) or (command == self.PIXTEND_SPI_GET_TEMP1) or ( command == self.PIXTEND_SPI_GET_TEMP2) or (command == self.PIXTEND_SPI_GET_TEMP3) or ( command == self.PIXTEND_SPI_GET_HUM0) or (command == self.PIXTEND_SPI_GET_HUM1) or ( command == self.PIXTEND_SPI_GET_HUM2) or (command == self.PIXTEND_SPI_GET_HUM3) or ( command == self.PIXTEND_SPI_SET_PWM0) or (command == self.PIXTEND_SPI_SET_PWM1) or ( command == self.PIXTEND_SPI_SET_AI_CTRL) or (command == self.PIXTEND_SPI_GET_UC_VER): to_send = [self.PIXTEND_SPI_HANDSHAKE, command, value0, value1, self.PIXTEND_SPI_NULL_BYTE] elif command == self.PIXTEND_SPI_SET_PWM_CTRL: to_send = [self.PIXTEND_SPI_HANDSHAKE, command, value0, value1, value2, self.PIXTEND_SPI_NULL_BYTE] elif command == self.PIXTEND_SPI_AUTO_MODE: to_send = auto_data else: raise Exception("__transfer_spi_data --> PIXTEND SPI command unknown!!!") # if self.__is_spi_open: resp = self.__spi.xfer2(to_send) # transfer byte data in one block with cs always active during transfer else: raise IOError("SPI not initialized!!! Use open method first!", "Method __transfer_spi_data was called!") return resp def __transfer_spi_dac_data(self, value0=0, value1=0): """ Transfer data to the DAC on the PiXtend board all in one block, the DAC does not return anything. The DAC expects 2 bytes in a special format, see MCP4812 manual for more details. :param int value0: First byte for the DAC :param int value1: Second byte for the DAC """ # Build data array to send to the DAC. to_send = [value0, value1] if self.__is_spi_dac_open: # transfer byte data in one block with cs always active during transfer resp = self.__spi_dac.xfer2(to_send) else: raise IOError("SPI for DAC not initialized!!! Use open_dac method first!", "Method __transfer_spi_dac_data was called!") return resp def __reset_microcontroller(self): """ DO NOT USE DURING NORMAL OPERATION - Internal function to reset the MC for testing """ GPIO.output(self.PIXTEND_MC_RESET_PIN, True) time.sleep(1) GPIO.output(self.PIXTEND_MC_RESET_PIN, False) time.sleep(1) def open(self, spi_channel=0, spi_cs=0, spi_speed=100000): """ Open SPI Master 0 with Chip Select 0 on the Raspberry Pi to start the communication with the microcontroller on the PiXtend board. :param int spi_channel: Number of the SPI master, default is 0, optional parameter :param int spi_cs: Chip Select (CS) for the SPI master, default is 0, optional parameter :param int spi_speed: SPI frequency, default 100 kHz, optional parameter :raises IOError: If SPI bus has already been opened """ self.__spi_channel = spi_channel self.__spi_cs = spi_cs self.__spi_speed = spi_speed # Open SPI bus if not self.__is_spi_open: self.__spi = spidev.SpiDev(self.__spi_channel, self.__spi_cs) self.__spi.open(self.__spi_channel, self.__spi_cs) self.__spi.max_speed_hz = self.__spi_speed self.__is_spi_open = True # Get the current board and firmware version right away self.__uc_version_get() else: raise IOError("Error: SPI 0 CS 0 already opened!") def open_dac(self, spi_channel=0, spi_cs=1, spi_speed=100000): """ Open SPI Master 0 with Chip Select 1 on the Raspberry Pi to start the communication with the DAC on the PiXtend board. :param int spi_channel: Number of the SPI master, default is 0, optional parameter :param int spi_cs: Chip Select (CS) for the SPI master for the DAC, default is 1, optional parameter :param int spi_speed: SPI frequency, default 100 kHz, optional parameter :raises IOError: If SPI bus has already been opened """ self.__spi_channel = spi_channel self.__spi_cs = spi_cs self.__spi_speed = spi_speed # Set the dac gain permanently to 0 # 0 = 2x (VOUT = 2 * VREF * D/4096), where internal VREF = 2.048V. self.__analog_dac_value.bits.bit1 = 0 # Set the dac output shutdown control bit permanently to 1 # 1 = Active mode operation. VOUT is available. self.__analog_dac_value.bits.bit2 = 1 # Open SPI bus if not self.__is_spi_dac_open: self.__spi_dac = spidev.SpiDev(self.__spi_channel, self.__spi_cs) self.__spi_dac.open(self.__spi_channel, self.__spi_cs) self.__spi_dac.max_speed_hz = self.__spi_speed self.__is_spi_dac_open = True else: raise IOError("SPI 0 CS 1 already opened!!!") def close(self): """ Close SPI device, clean up Raspberry Pi GPIO device and set all variables to None. """ try: self.__is_automatic_mode_active = None self.__use_fahrenheit = None self.__relays_states = None self.__digital_inputs_states = None self.__digital_outputs_states = None self.__analog_value = None self.__gpio_states_manual = None self.__gpio_states_auto_in = None self.__gpio_states_auto_out = None self.__servo0_value = None self.__servo1_value = None self.__pwm0_value = None self.__pwm1_value = None self.__pwm_ctrl0 = None self.__pwm_ctrl1 = None self.__pwm_ctrl2 = None self.__pwm_period = None self.__analog_dac_value = None self.__uc_ctrl = None self.__ai_ctrl0 = None self.__ai_ctrl1 = None self.__pi_status = None self.__ai0_jumper_setting_10_volts = None self.__ai1_jumper_setting_10_volts = None self.__ai0_raw_value = None self.__ai1_raw_value = None self.__ai2_raw_value = None self.__ai3_raw_value = None self.__temp0_raw_value = None self.__temp1_raw_value = None self.__temp2_raw_value = None self.__temp3_raw_value = None self.__humid0_raw_value = None self.__humid1_raw_value = None self.__humid2_raw_value = None self.__humid3_raw_value = None self.__ai0_value = None self.__ai1_value = None self.__ai2_value = None self.__ai3_value = None self.__temp0_value = None self.__temp1_value = None self.__temp2_value = None self.__temp3_value = None self.__humid0_value = None self.__humid1_value = None self.__humid2_value = None self.__humid3_value = None self.__analog0_dac_value = None self.__analog1_dac_value = None except: pass try: GPIO.cleanup() except: pass if self.__is_spi_open: try: self.__spi.close() except: pass self.__spi = None self.__is_spi_open = None if self.__is_spi_dac_open: try: self.__spi_dac.close() except: pass self.__spi_dac = None self.__is_spi_dac_open = None # # ************************************************************************** # Microcontroller Control Register, Version and Status # ************************************************************************** def __uc_control_register_set(self, value): """ Write one byte of data to the microcontroller's control register. See PiXtend manuals and AppNotes for more information on this topic. http://www.pixtend.de/files/manuals/AppNote_PiXtend_Control_Status_Bytes.pdf can be a good start. :param c_uint8 value: data to write to the microcontroller's control register """ if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_UC_CTRL, value) def __uc_status_register_get(self): """ Get the microcontroller's status register via SPI. :return: The value received from the microcontroller :rtype: c_uint8 """ # If the automatic mode is active, the data transfer is all done in the auto_mode method. if not self.__is_automatic_mode_active: value = self.__transfer_spi_data(self.PIXTEND_SPI_GET_UC_STAT) if len(value) >= 3: self.__uc_status = value[3] else: self.__uc_status = 0 return self.__uc_status def __uc_version_get(self): """ Get the board and firmware version from the microcontroller on the PiXtend board. """ # Only do the transfer if automatic mode is off if not self.__is_automatic_mode_active: value = self.__transfer_spi_data(self.PIXTEND_SPI_GET_UC_VER) if len(value) >= 4: self.__uc_version.UC_VERSIONL = value[3] self.__uc_version.UC_VERSIONH = value[4] @property def uc_status(self): """ Get the microcontroller status byte. :return: Status byte of the microcontroller on the PiXtend board :rtype: c_uint8 """ return self.__uc_status_register_get() @property def uc_board_version(self): """ Get the PiXtend board version. Example: A value of 13 means board version 1.3.x :return: Board version of the PiXtend board :rtype: c_uint8 """ # If the automatic mode is off, the board version will be fetched via SPI. if not self.__is_automatic_mode_active: self.__uc_version_get() return self.__uc_version.UC_VERSIONH @property def uc_fw_version(self): """ Get the microcontroller firmware version on the PiXtend board. Example: A value of 2 means firmware version 2.x is installed on the microcontroller :return: Firmware version of the microcontroller on the PiXtend board :rtype: c_uint8 """ # If the automatic mode is off, the firmware version will be fetched via SPI. if not self.__is_automatic_mode_active: self.__uc_version_get() return self.__uc_version.UC_VERSIONL @property def uc_control(self): """ Get or Set the microcontroller's control register. If the automatic mode is off, setting a new value will have immediate effect. :return: Current value of the control byte in the microcontroller :rtype: c_uint8 :raises ValueError: If the passed value is smaller then 0 or larger then 255 """ return self.__uc_ctrl.asByte @uc_control.setter def uc_control(self, value): if value >= 0 or value <= 255: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 to 255") self.__uc_ctrl.asByte = value self.__uc_control_register_set(value) # # # ************************************************************************** # GPIO Control - GPIO In/Out - DHT11/22 Settings # ************************************************************************** def __gpio_ctrl_set(self, value): """ Set the PiXtend GPIO control register to the value of 'value'. """ # If the automatic mode is active, the data transfer is all done in the auto_mode method. if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_GPIO_CTRL, value) def __gpio_ctrl_change_value(self, value, bit_num): """ Change one bit within the INT variable __gpio_ctrl based on value and bit_num. If value == 0 the bit at position bit_num is cleared, if value == 1 then the bit at position bit_num is set. :param int value: Value of the bit, 0 = off and 1 = on :param int bit_num: Bit to set or to clear, parameter is zero based """ if value == 0: self.__gpio_ctrl = self.__clear_bit(self.__gpio_ctrl, bit_num) if value == 1: self.__gpio_ctrl = self.__set_bit(self.__gpio_ctrl, bit_num) self.__gpio_ctrl_set(self.__gpio_ctrl) @property def gpio0_direction(self): """ Get or Set the direction of GPIO 0, input or output is possible. Example: p.gpio0_direction = p.GPIO_INPUT p.gpio0_direction = p.GPIO_OUTPUT or p.gpio0_direction = 0 # Input p.gpio0_direction = 1 # Output """ return self.__test_bit(self.__gpio_ctrl, self.BIT_0) @gpio0_direction.setter def gpio0_direction(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = Input, 1 = Output") self.__gpio_ctrl_change_value(value, self.BIT_0) @property def gpio1_direction(self): """ Get or Set the direction of GPIO 1, input or output is possible. Example: p.gpio1_direction = p.GPIO_INPUT p.gpio1_direction = p.GPIO_OUTPUT or p.gpio1_direction = 0 # Input p.gpio1_direction = 1 # Output """ return self.__test_bit(self.__gpio_ctrl, self.BIT_1) @gpio1_direction.setter def gpio1_direction(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = Input, 1 = Output") self.__gpio_ctrl_change_value(value, self.BIT_1) @property def gpio2_direction(self): """ Get or Set the direction of GPIO 2, input or output is possible. Example: p.gpio2_direction = p.GPIO_INPUT p.gpio2_direction = p.GPIO_OUTPUT or p.gpio2_direction = 0 # Input p.gpio2_direction = 1 # Output """ return self.__test_bit(self.__gpio_ctrl, self.BIT_2) @gpio2_direction.setter def gpio2_direction(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = Input, 1 = Output") self.__gpio_ctrl_change_value(value, self.BIT_2) @property def gpio3_direction(self): """ Get or Set the direction of GPIO 3, input or output is possible. Example: p.gpio3_direction = p.GPIO_INPUT p.gpio3_direction = p.GPIO_OUTPUT or p.gpio3_direction = 0 # Input p.gpio3_direction = 1 # Output """ return self.__test_bit(self.__gpio_ctrl, self.BIT_3) @gpio3_direction.setter def gpio3_direction(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = Input, 1 = Output") self.__gpio_ctrl_change_value(value, self.BIT_3) @property def dht0(self): """ Get or Set the 1-Wire setting for GPIO 0. Default is 'off' (0), if set to 'on' (1) means 1-wire sensors like DHT11, DHT22 and AM2302 can be used at this GPIO. The direction bit (input/output) will be ignored if this property is set to 'on' (1). """ return self.__test_bit(self.__gpio_ctrl, self.BIT_4) @dht0.setter def dht0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__gpio_ctrl_change_value(value, self.BIT_4) @property def dht1(self): """ Get or Set the 1-Wire setting for GPIO 1. Default is 'off' (0), if set to 'on' (1) means 1-wire sensors like DHT11, DHT22 and AM2302 can be used at this GPIO. The direction bit (input/output) will be ignored if this property is set to 'on' (1). """ return self.__test_bit(self.__gpio_ctrl, self.BIT_5) @dht1.setter def dht1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__gpio_ctrl_change_value(value, self.BIT_5) @property def dht2(self): """ Get or Set the 1-Wire setting for GPIO 2. Default is 'off' (0), if set to 'on' (1) means 1-wire sensors like DHT11, DHT22 and AM2302 can be used at this GPIO. The direction bit (input/output) will be ignored if this property is set to 'on' (1). """ return self.__test_bit(self.__gpio_ctrl, self.BIT_6) @dht2.setter def dht2(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__gpio_ctrl_change_value(value, self.BIT_6) @property def dht3(self): """ Get or Set the 1-Wire setting for GPIO 3. Default is 'off' (0), if set to 'on' (1) means 1-wire sensors like DHT11, DHT22 and AM2302 can be used at this GPIO. The direction bit (input/output) will be ignored if this property is set to 'on' (1). """ return self.__test_bit(self.__gpio_ctrl, self.BIT_7) @dht3.setter def dht3(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__gpio_ctrl_change_value(value, self.BIT_7) def __gpio_states_get(self): """ Get the current GPIO state from the microcontroller on the PiXtend board. """ # Only do SPI data transfer if the Auto Mode is not active. if not self.__is_automatic_mode_active: value = self.__transfer_spi_data(self.PIXTEND_SPI_GET_GPIO) if len(value) >= 3: self.__gpio_states_manual = value[3] else: self.__gpio_states_manual = 0 self.__gpio_states_auto_in = self.__gpio_states_manual return self.__gpio_states_manual else: return self.__gpio_states_auto_in def __gpio_states_set(self, value): """ Set the states of the GPIOs on the PiXtend board. """ resp = self.__transfer_spi_data(self.PIXTEND_SPI_SET_GPIO, value) return resp def __gpio_states_change_value(self, value, bit_num): """ Change the value of a single bit in the INT variable __gpio_states_manual given by bit_num to the value given by value. In Auto Mode the variable __gpio_states_auto_out is used instead. :param int value: Value of the bit, 0 = off and 1 = on :param int bit_num: Bit to set or to clear, parameter is zero based """ if not self.__is_automatic_mode_active: if value == 0: self.__gpio_states_manual = self.__clear_bit(self.__gpio_states_manual, bit_num) if value == 1: self.__gpio_states_manual = self.__set_bit(self.__gpio_states_manual, bit_num) self.__gpio_states_auto_out = self.__gpio_states_manual self.__gpio_states_set(self.__gpio_states_manual) else: if value == 0: self.__gpio_states_auto_out = self.__clear_bit(self.__gpio_states_auto_out, bit_num) if value == 1: self.__gpio_states_auto_out = self.__set_bit(self.__gpio_states_auto_out, bit_num) @property def gpio0(self): """ Get or Set the state of GPIO 0. The value 0 means 'off' and a value of 1 means 'on'. Example: p.gpio0 = p.ON # Turns the GPIO on p.gpio0 = p.OFF # Turns the GPIO off or use p.gpio0 = 1 # Turns the GPIO on p.gpio0 = 0 # Turns the GPIO off """ return self.__test_bit(self.__gpio_states_get(), self.BIT_0) @gpio0.setter def gpio0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") bit_num = self.BIT_0 if self.__test_bit(self.__gpio_ctrl, bit_num) == 1: self.__gpio_states_change_value(value, bit_num) else: raise IOError("IOError: GPIO 0 configured as INPUT! Cannot use as OUTPUT!", "- DirectionBit=" + str(self.__test_bit(self.__gpio_ctrl, bit_num))) @property def gpio1(self): """ Get or Set the state of GPIO 1. The value 0 means 'off' and a value of 1 means 'on'. Example: p.gpio1 = p.ON # Turns the GPIO on p.gpio1 = p.OFF # Turns the GPIO off or use p.gpio1 = 1 # Turns the GPIO on p.gpio1 = 0 # Turns the GPIO off """ return self.__test_bit(self.__gpio_states_get(), self.BIT_1) @gpio1.setter def gpio1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") bit_num = self.BIT_1 if self.__test_bit(self.__gpio_ctrl, bit_num) == 1: self.__gpio_states_change_value(value, bit_num) else: raise IOError("IOError: GPIO 1 configured as INPUT! Cannot use as OUTPUT!", "- DirectionBit=" + str(self.__test_bit(self.__gpio_ctrl, bit_num))) @property def gpio2(self): """ Get or Set the state of GPIO 2. The value 0 means 'off' and a value of 1 means 'on'. Example: p.gpio2 = p.ON # Turns the GPIO on p.gpio2 = p.OFF # Turns the GPIO off or use p.gpio2 = 1 # Turns the GPIO on p.gpio2 = 0 # Turns the GPIO off """ return self.__test_bit(self.__gpio_states_get(), self.BIT_2) @gpio2.setter def gpio2(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") bit_num = self.BIT_2 if self.__test_bit(self.__gpio_ctrl, bit_num) == 1: self.__gpio_states_change_value(value, bit_num) else: raise IOError("IOError: GPIO 2 configured as INPUT! Cannot use as OUTPUT!", "- DirectionBit=" + str(self.__test_bit(self.__gpio_ctrl, bit_num))) @property def gpio3(self): """ Get or Set the state of GPIO 3. The value 0 means 'off' and a value of 1 means 'on'. Example: p.gpio3 = p.ON # Turns the GPIO on p.gpio3 = p.OFF # Turns the GPIO off or use p.gpio3 = 1 # Turns the GPIO on p.gpio3 = 0 # Turns the GPIO off """ return self.__test_bit(self.__gpio_states_get(), self.BIT_3) @gpio3.setter def gpio3(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") bit_num = self.BIT_3 if self.__test_bit(self.__gpio_ctrl, bit_num) == 1: self.__gpio_states_change_value(value, bit_num) else: raise IOError("IOError: GPIO 3 configured as INPUT! Cannot use as OUTPUT!", "- DirectionBit=" + str(self.__test_bit(self.__gpio_ctrl, bit_num))) # # # ************************************************************************** # Servo Control # ************************************************************************** @property def servo0(self): """ Get or Set the value for PWM 0 in servo mode. Possible values are 0 to 250. """ return self.__servo0_value.value @servo0.setter def servo0(self, value): if 0 <= value <= 250: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use a value from 0 to 250") self.__servo0_value.value = value if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_SERVO0, self.__servo0_value.value) else: if self.pwm_ctrl_mode == 0: self.__pwm0_value.bytes.byte0 = value self.__pwm0_value.bytes.byte1 = 0 else: raise ValueError("Mode error! Servo mode was used, but outputs are set to PWM output.") @property def servo1(self): """ Get or Set the value for PWM 1 in servo mode. Possible values are 0 to 250. """ return self.__servo1_value.value @servo1.setter def servo1(self, value): if 0 <= value <= 250: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use a value from 0 to 250") self.__servo1_value.value = value if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_SERVO1, self.__servo1_value.value) else: if self.pwm_ctrl_mode == 0: self.__pwm1_value.bytes.byte0 = value self.__pwm1_value.bytes.byte1 = 0 else: raise ValueError("Mode error! Servo mode was used, but outputs are set to PWM output.") # # # ************************************************************************** # PWM Control # ************************************************************************** # Refer to the PiXtend's documentation for the 3 PWM Control registers # set_pwm_control: value0 = PWM_CTRL0, value1 = PWM_CTRL1, value2 = PWM_CTRL2 def __pwm_control_set(self, value0, value1, value2): """ Set the PWM control registers in the microcontroller on the PiXtend board to the values of value0, value1 and value2. """ resp = self.__transfer_spi_data(self.PIXTEND_SPI_SET_PWM_CTRL, value0, value1, value2) return resp def pwm_ctrl_configure(self): """ Configures the PWM control of the microcontroller on the PiXtend board. The data transferred includes all PWM settings like the Mode, OD setting and the Clock Select for the PWMs as well as the frequency. """ self.__pwm_ctrl1.asByte = self.__pwm_period.bytes.byte0 self.__pwm_ctrl2.asByte = self.__pwm_period.bytes.byte1 if not self.__is_automatic_mode_active: self.__pwm_control_set(self.__pwm_ctrl0.asByte, self.__pwm_ctrl1.asByte, self.__pwm_ctrl2.asByte) @property def pwm_ctrl_mode(self): """ Get or Set the PWM mode. A value of 0 means the PWMs are in servo mode, a value of 1 means both PWMs are in PWM mode. """ return self.__pwm_ctrl0.b.bit0 @pwm_ctrl_mode.setter def pwm_ctrl_mode(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = servo or 1 = pwm") self.__pwm_ctrl0.b.bit0 = value @property def pwm_ctrl_od0(self): """ Get or Set the Over Drive (OD) value for PWM 0. A value of 0 means 'off' and 1 means 'on'. """ return self.__pwm_ctrl0.b.bit1 @pwm_ctrl_od0.setter def pwm_ctrl_od0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 or 1") self.__pwm_ctrl0.b.bit1 = value @property def pwm_ctrl_od1(self): """ Get or Set the Over Drive (OD) value for PWM 1. A value of 0 means 'off' and 1 means 'on'. """ return self.__pwm_ctrl0.b.bit2 @pwm_ctrl_od1.setter def pwm_ctrl_od1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 or 1") self.__pwm_ctrl0.b.bit2 = value @property def pwm_ctrl_cs0(self): """ Get or Set the Clock Select bit 0 (CS0) for both PWMs. This setting will be ignored if PWMs are in servo mode. A value of 1 means this CS is 'on' (selected) and 0 means 'off' (not selected). """ return self.__pwm_ctrl0.b.bit5 @pwm_ctrl_cs0.setter def pwm_ctrl_cs0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 or 1") self.__pwm_ctrl0.b.bit5 = value @property def pwm_ctrl_cs1(self): """ Get or Set the Clock Select bit 1 (CS1) for both PWMs. This setting will be ignored if PWMs are in servo mode. A value of 1 means this CS is 'on' (selected) and 0 means 'off' (not selected). """ return self.__pwm_ctrl0.b.bit6 @pwm_ctrl_cs1.setter def pwm_ctrl_cs1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 or 1") self.__pwm_ctrl0.b.bit6 = value @property def pwm_ctrl_cs2(self): """ Get or Set the Clock Select bit 2 (CS2) for both PWMs. This setting will be ignored if PWMs are in servo mode. A value of 1 means this CS is 'on' (selected) and 0 means 'off' (not selected). """ return self.__pwm_ctrl0.b.bit7 @pwm_ctrl_cs2.setter def pwm_ctrl_cs2(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 or 1") self.__pwm_ctrl0.b.bit7 = value @property def pwm_ctrl_period(self): """ Get or Set the frequency/signal period length of the PWMs. Values from 0 to 65000 are allowed. Example: pwm_ctrl_cs0 = 1, pwm_ctrl_cs1 = 1, pwm_ctrl_cs2 = 0, pwm_ctrl_mode = 1 and pwm_ctrl_period = 1000 PWM Period length = uMC Freq / Prescaler / PWM_CTRL1..2 PWM Period length = 16 MHz / 64 / 1000 = 250 Hz """ return self.__pwm_period.asUint16 @pwm_ctrl_period.setter def pwm_ctrl_period(self, value): if 0 <= value <= 65000: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use a value from 0 to 65000") self.__pwm_period.asUint16 = value @property def pwm0(self): """ Get or Set the PWM 0 duty cycle. This value has to correspond to the PWM freq/signal period configuration. The allowed values for PWM 0 are 0 to 65000. Example: pwm_ctrl_cs0 = 1, pwm_ctrl_cs1 = 1, pwm_ctrl_cs2 = 0, pwm_ctrl_mode = 1 and pwm_ctrl_period = 5000 * If pwm0 is set to 2500 the duty cycle will be 50% * If pwm0 > pwm_ctrl_period the PWM channel will be continuously logical 1 * If pwm0 = 0 the PWM channel will be continuously logical 0 """ return self.__pwm0_value.asUint16 @pwm0.setter def pwm0(self, value): if 0 <= value <= 65000: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use a value from 0 to 65000") self.__pwm0_value.asUint16 = value if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_PWM0, self.__pwm0_value.bytes.byte0, self.__pwm0_value.bytes.byte1) @property def pwm1(self): """ Get or Set the PWM 1 duty cycle. This value has to correspond to the PWM freq/signal period configuration. The allowed values for PWM 1 are 0 to 65000. Example: pwm_ctrl_cs0 = 1, pwm_ctrl_cs1 = 1, pwm_ctrl_cs2 = 0, pwm_ctrl_mode = 1 and pwm_ctrl_period = 5000 * If pwm1 is set to 2500 the duty cycle will be 50%. * If pwm1 > pwm_ctrl_period the PWM channel will be continuously logical 1 * If pwm1 = 0 the PWM channel will be continuously logical 0 """ return self.__pwm1_value.asUint16 @pwm1.setter def pwm1(self, value): if 0 <= value <= 65000: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use a value from 0 to 65000") self.__pwm1_value.asUint16 = value if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_PWM1, self.__pwm1_value.bytes.byte0, self.__pwm1_value.bytes.byte1) # # # ************************************************************************** # Analog Inputs 0 - 3 # ************************************************************************** def __analog_input_register_set(self): """ Set the analog control register of the microcontroller on the PiXtend board. """ self.__transfer_spi_data(self.PIXTEND_SPI_SET_AI_CTRL, self.__ai_ctrl0.asByte, self.__ai_ctrl1.asByte) def __analog_input_get(self, command): """ Get the analog value from one of the 4 analog inputs on the PiXtend board. :param int command: One of the 4 PIXTEND_SPI_GET_AINx constants values specifying AI0-3 :return: A 16 bit value representing the analog value of the requested AIx :rtype: c_uint16 """ # Only transfer data is Auto Mode is not active. if not self.__is_automatic_mode_active: # Wait, this method might have been called before. The microcontroller needs time for processing. time.sleep(0.05) self.__transfer_spi_data(command) # Wait again after the first transfer, the microcontroller now needs time to start and complete its A/D # conversion. Then the 'real' data can be requested. time.sleep(0.05) resp = self.__transfer_spi_data(command) # Wait for security in case this or another method with SPI data transfer is called right after this one. time.sleep(0.05) if len(resp) >= 4: self.__analog_value.bytes.byte0 = resp[3] self.__analog_value.bytes.byte1 = resp[4] else: self.__analog_value.asUint16 = 0 else: if command == self.PIXTEND_SPI_GET_AIN0: self.__analog_value.asUint16 = self.__ai0_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_AIN1: self.__analog_value.asUint16 = self.__ai1_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_AIN2: self.__analog_value.asUint16 = self.__ai2_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_AIN3: self.__analog_value.asUint16 = self.__ai3_raw_value.asUint16 else: self.__analog_value.asUint16 = 0 return self.__analog_value.asUint16 @property def analog_input0_raw(self): """ Get the raw value of analog input 0. """ return self.__analog_input_get(self.PIXTEND_SPI_GET_AIN0) @property def analog_input1_raw(self): """ Get the raw value of analog input 1. """ return self.__analog_input_get(self.PIXTEND_SPI_GET_AIN1) @property def analog_input2_raw(self): """ Get the raw value of analog input 2. """ return self.__analog_input_get(self.PIXTEND_SPI_GET_AIN2) @property def analog_input3_raw(self): """ Get the raw value of analog input 3. """ return self.__analog_input_get(self.PIXTEND_SPI_GET_AIN3) @property def analog_input0_10volts_jumper(self): """ Get or Set the 10 volts jumper setting, depending if the jumper was physically set on the PiXtend board. The library needs to know this setting to perform correct calculations of the raw analog values of the analog inputs when they are converted their final float value. Applies only to AI0 and AI1 as these 2 inputs measure voltage. """ return self.__ai0_jumper_setting_10_volts @analog_input0_10volts_jumper.setter def analog_input0_10volts_jumper(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 0 = 5 volts, 1 = 10 volts") self.__ai0_jumper_setting_10_volts = value @property def analog_input1_10volts_jumper(self): """ Get or Set the 10 volts jumper setting, depending if the jumper was physically set on the PiXtend board. The library needs to know this setting to perform correct calculations of the raw analog values of the analog inputs when they are converted their final float value. Applies only to AI0 and AI1 as these 2 inputs measure voltage. """ return self.__ai1_jumper_setting_10_volts @analog_input1_10volts_jumper.setter def analog_input1_10volts_jumper(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 0 = 5 volts, 1 = 10 volts") self.__ai1_jumper_setting_10_volts = value @property def analog_input0_nos(self): """ Get or Set the Number of Samples (NoS) the analog input 0 should take from the incoming analog signal. Possible NoS values are 1, 5, 10 (default) and 50. If something is wrong -1 is returned. :return: Decimal value of the Number of Samples (NoS) :rtype: int :raises ValueError: If passed value is not 1, 5, 10, 50 """ if self.__ai_ctrl0.b.bit0 == 0 and self.__ai_ctrl0.b.bit1 == 0: return 10 elif self.__ai_ctrl0.b.bit0 == 1 and self.__ai_ctrl0.b.bit1 == 0: return 1 elif self.__ai_ctrl0.b.bit0 == 0 and self.__ai_ctrl0.b.bit1 == 1: return 5 elif self.__ai_ctrl0.b.bit0 == 1 and self.__ai_ctrl0.b.bit1 == 1: return 50 else: return -1 @analog_input0_nos.setter def analog_input0_nos(self, value): if value == 1 or value == 5 or value == 10 or value == 50: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 1, 5, 10 or 50 NoS") if value == 1: self.__ai_ctrl0.b.bit0 = 1 self.__ai_ctrl0.b.bit1 = 0 elif value == 5: self.__ai_ctrl0.b.bit0 = 0 self.__ai_ctrl0.b.bit1 = 1 elif value == 10: self.__ai_ctrl0.b.bit0 = 0 self.__ai_ctrl0.b.bit1 = 0 elif value == 50: self.__ai_ctrl0.b.bit0 = 1 self.__ai_ctrl0.b.bit1 = 1 else: self.__ai_ctrl0.b.bit0 = 0 self.__ai_ctrl0.b.bit1 = 0 if not self.__is_automatic_mode_active: self.__analog_input_register_set() @property def analog_input1_nos(self): """ Get or Set the Number of Samples (NoS) the analog input 1 should take from the incoming analog signal. Possible NoS values are 1, 5, 10 (default) and 50. If something is wrong -1 is returned. :return: Decimal value of the Number of Samples (NoS) :rtype: int :raises ValueError: If passed value is not 1, 5, 10, 50 """ if self.__ai_ctrl0.b.bit2 == 0 and self.__ai_ctrl0.b.bit3 == 0: return 10 elif self.__ai_ctrl0.b.bit2 == 1 and self.__ai_ctrl0.b.bit3 == 0: return 1 elif self.__ai_ctrl0.b.bit2 == 0 and self.__ai_ctrl0.b.bit3 == 1: return 5 elif self.__ai_ctrl0.b.bit2 == 1 and self.__ai_ctrl0.b.bit3 == 1: return 50 else: return -1 @analog_input1_nos.setter def analog_input1_nos(self, value): if value == 1 or value == 5 or value == 10 or value == 50: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 1, 5, 10 or 50 NoS") if value == 1: self.__ai_ctrl0.b.bit2 = 1 self.__ai_ctrl0.b.bit3 = 0 elif value == 5: self.__ai_ctrl0.b.bit2 = 0 self.__ai_ctrl0.b.bit3 = 1 elif value == 10: self.__ai_ctrl0.b.bit2 = 0 self.__ai_ctrl0.b.bit3 = 0 elif value == 50: self.__ai_ctrl0.b.bit2 = 1 self.__ai_ctrl0.b.bit3 = 1 else: self.__ai_ctrl0.b.bit2 = 0 self.__ai_ctrl0.b.bit3 = 0 if not self.__is_automatic_mode_active: self.__analog_input_register_set() @property def analog_input2_nos(self): """ Get or Set the Number of Samples (NoS) the analog input 2 should take from the incoming analog signal. Possible NoS values are 1, 5, 10 (default) and 50. If something is wrong -1 is returned. :return: Decimal value of the Number of Samples (NoS) :rtype: int :raises ValueError: If passed value is not 1, 5, 10, 50 """ if self.__ai_ctrl0.b.bit4 == 0 and self.__ai_ctrl0.b.bit5 == 0: return 10 elif self.__ai_ctrl0.b.bit4 == 1 and self.__ai_ctrl0.b.bit5 == 0: return 1 elif self.__ai_ctrl0.b.bit4 == 0 and self.__ai_ctrl0.b.bit5 == 1: return 5 elif self.__ai_ctrl0.b.bit4 == 1 and self.__ai_ctrl0.b.bit5 == 1: return 50 else: return -1 @analog_input2_nos.setter def analog_input2_nos(self, value): if value == 1 or value == 5 or value == 10 or value == 50: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 1, 5, 10 or 50 NoS") if value == 1: self.__ai_ctrl0.b.bit4 = 1 self.__ai_ctrl0.b.bit5 = 0 elif value == 5: self.__ai_ctrl0.b.bit4 = 0 self.__ai_ctrl0.b.bit5 = 1 elif value == 10: self.__ai_ctrl0.b.bit4 = 0 self.__ai_ctrl0.b.bit5 = 0 elif value == 50: self.__ai_ctrl0.b.bit4 = 1 self.__ai_ctrl0.b.bit5 = 1 else: self.__ai_ctrl0.b.bit4 = 0 self.__ai_ctrl0.b.bit5 = 0 if not self.__is_automatic_mode_active: self.__analog_input_register_set() @property def analog_input3_nos(self): """ Get or Set the Number of Samples (NoS) the analog input 3 should take from the incoming analog signal. Possible NoS values are 1, 5, 10 (default) and 50. If something is wrong -1 is returned. :return: Decimal value of the Number of Samples (NoS) :rtype: int :raises ValueError: If passed value is not 1, 5, 10, 50 """ if self.__ai_ctrl0.b.bit6 == 0 and self.__ai_ctrl0.b.bit7 == 0: return 10 elif self.__ai_ctrl0.b.bit6 == 1 and self.__ai_ctrl0.b.bit7 == 0: return 1 elif self.__ai_ctrl0.b.bit6 == 0 and self.__ai_ctrl0.b.bit7 == 1: return 5 elif self.__ai_ctrl0.b.bit6 == 1 and self.__ai_ctrl0.b.bit7 == 1: return 50 else: return -1 @analog_input3_nos.setter def analog_input3_nos(self, value): if value == 1 or value == 5 or value == 10 or value == 50: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use: 1, 5, 10 or 50 NoS") if value == 1: self.__ai_ctrl0.b.bit6 = 1 self.__ai_ctrl0.b.bit7 = 0 elif value == 5: self.__ai_ctrl0.b.bit6 = 0 self.__ai_ctrl0.b.bit7 = 1 elif value == 10: self.__ai_ctrl0.b.bit6 = 0 self.__ai_ctrl0.b.bit7 = 0 elif value == 50: self.__ai_ctrl0.b.bit6 = 1 self.__ai_ctrl0.b.bit7 = 1 else: self.__ai_ctrl0.b.bit6 = 0 self.__ai_ctrl0.b.bit7 = 0 if not self.__is_automatic_mode_active: self.__analog_input_register_set() @property def analog_input_nos_freq(self): """ Get or Set the Clock Select of the A/D converter of the microcontroller on the PiXtend board. Possible float values are 0.125, 0.250, 0.500, 1.0, 2.0, 4.0 and 8.0. The unit is Mhz. :return: Float value of the currently set A/D converter frequency :rtype: float """ if self.__ai_ctrl1.b.bit5 == 0 and self.__ai_ctrl1.b.bit6 == 0 and self.__ai_ctrl1.b.bit7 == 0: return 0.125 elif self.__ai_ctrl1.b.bit5 == 0 and self.__ai_ctrl1.b.bit6 == 1 and self.__ai_ctrl1.b.bit7 == 1: return 0.250 elif self.__ai_ctrl1.b.bit5 == 1 and self.__ai_ctrl1.b.bit6 == 0 and self.__ai_ctrl1.b.bit7 == 1: return 0.500 elif self.__ai_ctrl1.b.bit5 == 0 and self.__ai_ctrl1.b.bit6 == 0 and self.__ai_ctrl1.b.bit7 == 1: return 1.0 elif self.__ai_ctrl1.b.bit5 == 1 and self.__ai_ctrl1.b.bit6 == 1 and self.__ai_ctrl1.b.bit7 == 0: return 2.0 elif self.__ai_ctrl1.b.bit5 == 0 and self.__ai_ctrl1.b.bit6 == 1 and self.__ai_ctrl1.b.bit7 == 0: return 4.0 elif self.__ai_ctrl1.b.bit5 == 1 and self.__ai_ctrl1.b.bit6 == 0 and self.__ai_ctrl1.b.bit7 == 0: return 8.0 else: return -1.0 @analog_input_nos_freq.setter def analog_input_nos_freq(self, value): if value == 0.125 or value == 0.250 or value == 0.500 or value == 1.0 or \ value == 2.0 or value == 4.0 or value == 8.0: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use (MHz): 0.125, 0.250, 0.500, 1.0, 2.0, 4.0 or 8.0") if value == 0.125: self.__ai_ctrl1.b.bit5 = 0 self.__ai_ctrl1.b.bit6 = 0 self.__ai_ctrl1.b.bit7 = 0 elif value == 0.250: self.__ai_ctrl1.b.bit5 = 0 self.__ai_ctrl1.b.bit6 = 1 self.__ai_ctrl1.b.bit7 = 1 elif value == 0.500: self.__ai_ctrl1.b.bit5 = 1 self.__ai_ctrl1.b.bit6 = 0 self.__ai_ctrl1.b.bit7 = 1 elif value == 1.0: self.__ai_ctrl1.b.bit5 = 0 self.__ai_ctrl1.b.bit6 = 0 self.__ai_ctrl1.b.bit7 = 1 elif value == 2.0: self.__ai_ctrl1.b.bit5 = 1 self.__ai_ctrl1.b.bit6 = 1 self.__ai_ctrl1.b.bit7 = 0 elif value == 4.0: self.__ai_ctrl1.b.bit5 = 0 self.__ai_ctrl1.b.bit6 = 1 self.__ai_ctrl1.b.bit7 = 0 elif value == 8.0: self.__ai_ctrl1.b.bit5 = 1 self.__ai_ctrl1.b.bit6 = 0 self.__ai_ctrl1.b.bit7 = 0 else: self.__ai_ctrl1.b.bit5 = 0 self.__ai_ctrl1.b.bit6 = 0 self.__ai_ctrl1.b.bit7 = 0 if not self.__is_automatic_mode_active: self.__analog_input_register_set() @property def analog_input0(self): """ Get analog input 0 value as float in Volts. The returned value is based on the 10 volts jumper setting. """ # Only get data directly if Auto Mode is not active. if not self.__is_automatic_mode_active: if self.__ai0_jumper_setting_10_volts == 1: self.__ai0_value = c_float(self.analog_input0_raw * (10.0 / 1024)) else: self.__ai0_value = c_float(self.analog_input0_raw * (5.0 / 1024)) return self.__ai0_value.value @property def analog_input1(self): """ Get analog input 1 value as float in Volts. The returned value is based on the 10 volts jumper setting. """ # Only get data directly if Auto Mode is not active. if not self.__is_automatic_mode_active: if self.__ai1_jumper_setting_10_volts == 1: self.__ai1_value = c_float(self.analog_input1_raw * (10.0 / 1024)) else: self.__ai1_value = c_float(self.analog_input1_raw * (5.0 / 1024)) return self.__ai1_value.value @property def analog_input2(self): """ Get analog input 2 value as float in Ampere (mA). """ # Only get data directly if Auto Mode is not active. if not self.__is_automatic_mode_active: self.__ai2_value = c_float(self.analog_input2_raw * 0.024194115990990990990990990991) return self.__ai2_value.value @property def analog_input3(self): """ Get analog input 3 value as float in Ampere (mA). """ # Only get data directly if Auto Mode is not active. if not self.__is_automatic_mode_active: self.__ai3_value = c_float(self.analog_input3_raw * 0.024194115990990990990990990991) return self.__ai3_value.value # # # ************************************************************************** # Temperature Inputs 0 - 3 # ************************************************************************** def __temperature_get(self, command): """ Get the current temperature from the GPIO specified in 'command'. This is only possible if the corresponding GPIO is set to DHT mode. :param int command: One of 4 possible constant values defined by PIXTEND_SPI_GET_TEMPx :return: The 16 bit raw temperature value from the sensor attached to the GPIO :rtype: c_uint16 """ if not self.__is_automatic_mode_active: # Wait, this method might have been called before. The microcontroller needs time for processing. time.sleep(0.1) self.__transfer_spi_data(command) # Wait again after the first transfer/request, the microcontroller now needs time to start and complete # its 1-wire communication with the sensor attached to the requested GPIO. # Then the 'real' data can be requested. time.sleep(0.2) resp = self.__transfer_spi_data(command) # Wait for security in case this or another method with SPI data transfer is called right after this one. time.sleep(0.1) if len(resp) >= 4: self.__analog_value.bytes.byte0 = resp[3] self.__analog_value.bytes.byte1 = resp[4] else: self.__analog_value.asUint16 = 0 else: if command == self.PIXTEND_SPI_GET_TEMP0: self.__analog_value.asUint16 = self.__temp0_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_TEMP1: self.__analog_value.asUint16 = self.__temp1_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_TEMP2: self.__analog_value.asUint16 = self.__temp2_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_TEMP3: self.__analog_value.asUint16 = self.__temp3_raw_value.asUint16 else: self.__analog_value.asUint16 = 0 return self.__analog_value.asUint16 @property def use_fahrenheit(self): """ Get or Set if the conversion of the temperature raw value should be done in Fahrenheit. Default is 'False', meaning by getting the temperature from t0_dht22 to t3_dht22 or from t0_dht11 to t3_dht11 the value will be in degrees Celsius, if set to 'True' the values will be in Fahrenheit. :returns: Bool value, 'False' for Celsius and 'True' for Fahrenheit :rtype: bool """ return self.__use_fahrenheit @use_fahrenheit.setter def use_fahrenheit(self, value): self.__use_fahrenheit = value @property def temp_input0_raw(self): """ Get the temperature raw value from temperature input 0. """ return self.__temperature_get(self.PIXTEND_SPI_GET_TEMP0) @property def temp_input1_raw(self): """ Get the temperature raw value from temperature input 1. """ return self.__temperature_get(self.PIXTEND_SPI_GET_TEMP1) @property def temp_input2_raw(self): """ Get the temperature raw value from temperature input 2. """ return self.__temperature_get(self.PIXTEND_SPI_GET_TEMP2) @property def temp_input3_raw(self): """ Get the temperature raw value from temperature input 3. """ return self.__temperature_get(self.PIXTEND_SPI_GET_TEMP3) @property def t0_dht22(self): """ Get the converted temperature value from temperature input 0 from a DHT22 sensor. """ factor = 1.0 value = self.temp_input0_raw if self.__test_bit(value, self.BIT_15) == 1: value = self.__clear_bit(value, self.BIT_15) factor = -1.0 if self.__use_fahrenheit: return ((float(value) * 1.8) + 32) * factor else: return (float(value) / 10.0) * factor @property def t1_dht22(self): """ Get the converted temperature value from temperature input 1 from a DHT22 sensor. """ factor = 1.0 value = self.temp_input1_raw if self.__test_bit(value, self.BIT_15) == 1: value = self.__clear_bit(value, self.BIT_15) factor = -1.0 if self.__use_fahrenheit: return ((float(value) * 1.8) + 32) * factor else: return (float(value) / 10.0) * factor @property def t2_dht22(self): """ Get the converted temperature value from temperature input 2 from a DHT22 sensor. """ factor = 1.0 value = self.temp_input2_raw if self.__test_bit(value, self.BIT_15) == 1: value = self.__clear_bit(value, self.BIT_15) factor = -1.0 if self.__use_fahrenheit: return ((float(value) * 1.8) + 32) * factor else: return (float(value) / 10.0) * factor @property def t3_dht22(self): """ Get the converted temperature value from temperature input 3 from a DHT22 sensor. """ factor = 1.0 value = self.temp_input3_raw if self.__test_bit(value, self.BIT_15) == 1: value = self.__clear_bit(value, self.BIT_15) factor = -1.0 if self.__use_fahrenheit: return ((float(value) * 1.8) + 32) * factor else: return (float(value) / 10.0) * factor @property def t0_dht11(self): """ Get the converted temperature value from temperature input 0 from a DHT11 sensor. """ if self.__use_fahrenheit: return (float(self.temp_input0_raw / 256) * 1.8) + 32 else: return float(self.temp_input0_raw / 256) @property def t1_dht11(self): """ Get the converted temperature value from temperature input 1 from a DHT11 sensor. """ if self.__use_fahrenheit: return (float(self.temp_input1_raw / 256) * 1.8) + 32 else: return float(self.temp_input1_raw / 256) @property def t2_dht11(self): """ Get the converted temperature value from temperature input 2 from a DHT11 sensor. """ if self.__use_fahrenheit: return (float(self.temp_input2_raw / 256) * 1.8) + 32 else: return float(self.temp_input2_raw / 256) @property def t3_dht11(self): """ Get the converted temperature value from temperature input 3 from a DHT11 sensor. """ if self.__use_fahrenheit: return (float(self.temp_input3_raw / 256) * 1.8) + 32 else: return float(self.temp_input3_raw / 256) # # # ************************************************************************** # Humidity Inputs 0 - 3 # ************************************************************************** def __humidity_get(self, command): """ Get the current humidity from the GPIO specified in 'command'. This is only possible if the corresponding GPIO is set to DHT (1-wire) mode. :param int command: One of 4 possible constant values defined by PIXTEND_SPI_GET_HUMx :return: The 16 bit raw humidity value from the sensor attached to the GPIO :rtype: c_uint16 """ if not self.__is_automatic_mode_active: # Wait, this method might have been called before. The microcontroller needs time for processing. time.sleep(0.1) self.__transfer_spi_data(command) # Wait again after the first transfer/request, the microcontroller now needs time to start and complete # its 1-wire communication with the sensor attached to the requested GPIO. # Then the 'real' data can be requested. time.sleep(0.2) value = self.__transfer_spi_data(command) # Wait for security in case this or another method with SPI data transfer is called right after this one. time.sleep(0.1) if len(value) >= 4: self.__analog_value.bytes.byte0 = value[3] self.__analog_value.bytes.byte1 = value[4] else: self.__analog_value.asUint16 = 0 else: if command == self.PIXTEND_SPI_GET_HUM0: self.__analog_value.asUint16 = self.__humid0_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_HUM1: self.__analog_value.asUint16 = self.__humid1_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_HUM2: self.__analog_value.asUint16 = self.__humid2_raw_value.asUint16 elif command == self.PIXTEND_SPI_GET_HUM3: self.__analog_value.asUint16 = self.__humid3_raw_value.asUint16 else: self.__analog_value.asUint16 = 0 return self.__analog_value.asUint16 @property def hum_input0_raw(self): """ Get the humidity raw value from humidity input 0. """ return self.__humidity_get(self.PIXTEND_SPI_GET_HUM0) @property def hum_input1_raw(self): """ Get the humidity raw value from humidity input 1. """ return self.__humidity_get(self.PIXTEND_SPI_GET_HUM1) @property def hum_input2_raw(self): """ Get the humidity raw value from humidity input 2. """ return self.__humidity_get(self.PIXTEND_SPI_GET_HUM2) @property def hum_input3_raw(self): """ Get the humidity raw value from humidity input 3. """ return self.__humidity_get(self.PIXTEND_SPI_GET_HUM3) @property def h0_dht22(self): """ Get the converted humidity value from humidity input 0 if a DHT22 sensor is physically attached. """ return float(self.hum_input0_raw) / 10.0 @property def h1_dht22(self): """ Get the converted humidity value from humidity input 1 if a DHT22 sensor is physically attached. """ return float(self.hum_input1_raw) / 10.0 @property def h2_dht22(self): """ Get the converted humidity value from humidity input 2 if a DHT22 sensor is physically attached. """ return float(self.hum_input2_raw) / 10.0 @property def h3_dht22(self): """ Get the converted humidity value from humidity input 3 if a DHT22 sensor is physically attached. """ return float(self.hum_input3_raw) / 10.0 @property def h0_dht11(self): """ Get the converted humidity value from humidity input 0 if a DHT11 sensor is physically attached. """ return float(self.hum_input0_raw / 256) @property def h1_dht11(self): """ Get the converted humidity value from humidity input 1 if a DHT11 sensor is physically attached. """ return float(self.hum_input1_raw / 256) @property def h2_dht11(self): """ Get the converted humidity value from humidity input 2 if a DHT11 sensor is physically attached. """ return float(self.hum_input2_raw / 256) @property def h3_dht11(self): """ Get the converted humidity value from humidity input 3 if a DHT11 sensor is physically attached. """ return float(self.hum_input3_raw / 256) # # # ************************************************************************** # Digital Outputs # ************************************************************************** def __digital_outputs_states_get(self): """ Get the states of the digital outputs. Since PiXtend board version 1.3.x the digital outputs have the capability to read back their own state. """ # Only do an SPI data transfer if Auto Mode is not active and the PiXtend board version is higher # then 12, meaning version 1.2.x. Only boards starting from version 1.3.0 can read back their own outputs. if not self.__is_automatic_mode_active and self.__uc_version.UC_VERSIONH > 12: resp = self.__transfer_spi_data(self.PIXTEND_SPI_GET_DOUT) if len(resp) >= 3: self.__digital_outputs_states = resp[3] else: self.__digital_outputs_states = 0 return self.__digital_outputs_states def __digital_outputs_states_set(self, value, bit_num): if value == 0: self.__digital_outputs_states = self.__clear_bit(self.__digital_outputs_states, bit_num) if value == 1: self.__digital_outputs_states = self.__set_bit(self.__digital_outputs_states, bit_num) # Only do an SPI data transfer if Auto Mode is not active. if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_DOUT, self.__digital_outputs_states) @property def digital_output0(self): """ Get or Set the state of digital output 0. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_0) @digital_output0.setter def digital_output0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_0) @property def do0(self): """ Get or Set the state of digital output 0. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output0 <--> do0 """ return self.digital_output0 @do0.setter def do0(self, value): self.digital_output0 = value @property def digital_output1(self): """ Get or Set the state of digital output 1. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_1) @digital_output1.setter def digital_output1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_1) @property def do1(self): """ Get or Set the state of digital output 1. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output1 <--> do1 """ return self.digital_output1 @do1.setter def do1(self, value): self.digital_output1 = value @property def digital_output2(self): """ Get or Set the state of digital output 2. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_2) @digital_output2.setter def digital_output2(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_2) @property def do2(self): """ Get or Set the state of digital output 2. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output2 <--> do2 """ return self.digital_output2 @do2.setter def do2(self, value): self.digital_output2 = value @property def digital_output3(self): """ Get or Set the state of digital output 3. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_3) @digital_output3.setter def digital_output3(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_3) @property def do3(self): """ Get or Set the state of digital output 3. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output3 <--> do3 """ return self.digital_output3 @do3.setter def do3(self, value): self.digital_output3 = value @property def digital_output4(self): """ Get or Set the state of digital output 4. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_4) @digital_output4.setter def digital_output4(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_4) @property def do4(self): """ Get or Set the state of digital output 4. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output4 <--> do4 """ return self.digital_output4 @do4.setter def do4(self, value): self.digital_output4 = value @property def digital_output5(self): """ Get or Set the state of digital output 5. A value 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_outputs_states_get(), self.BIT_5) @digital_output5.setter def digital_output5(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__digital_outputs_states_set(value, self.BIT_5) @property def do5(self): """ Get or Set the state of digital output 5. A value 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_output5 <--> do5 """ return self.digital_output5 @do5.setter def do5(self, value): self.digital_output5 = value # # # ************************************************************************** # Relay Outputs # ************************************************************************** def __relays_states_get(self): """ Get the current states of the 4 relays on the PiXtend board. """ # Only do an SPI data transfer if Auto Mode is not active. if not self.__is_automatic_mode_active: resp = self.__transfer_spi_data(self.PIXTEND_SPI_GET_RELAY) if len(resp) >= 3: self.__relays_states = resp[3] else: self.__relays_states = 0 return self.__relays_states def __relays_states_set(self, value, bit_num): """ Set the current states of the 4 relays on the PiXtend board. :param int value: Value the specified bit should have, 0 or 1 :param int bit_num: Number of the bit to change """ if value == 0: self.__relays_states = self.__clear_bit(self.__relays_states, bit_num) if value == 1: self.__relays_states = self.__set_bit(self.__relays_states, bit_num) # Only do an SPI data transfer if Auto Mode is not active. if not self.__is_automatic_mode_active: self.__transfer_spi_data(self.PIXTEND_SPI_SET_RELAY, self.__relays_states) @property def relay0(self): """ Get or Set the state of relay 0. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__relays_states_get(), self.BIT_0) @relay0.setter def relay0(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__relays_states_set(value, self.BIT_0) @property def relay1(self): """ Get or Set the state of relay 1. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__relays_states_get(), self.BIT_1) @relay1.setter def relay1(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__relays_states_set(value, self.BIT_1) @property def relay2(self): """ Get or Set the state of relay 2. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__relays_states_get(), self.BIT_2) @relay2.setter def relay2(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__relays_states_set(value, self.BIT_2) @property def relay3(self): """ Get or Set the state of relay 3. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__relays_states_get(), self.BIT_3) @relay3.setter def relay3(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = off, 1 = on") self.__relays_states_set(value, self.BIT_3) # # # ************************************************************************** # Digital Inputs # ************************************************************************** def __digital_inputs_get(self): """ Get the current states of the 8 digital inputs on the PiXtend board. """ # In manual mode (__is_automatic_mode_active == False), get the current states of the # digital inputs directly via SPI request. In Auto Mode, simply return the int with the states. if not self.__is_automatic_mode_active: value = self.__transfer_spi_data(self.PIXTEND_SPI_GET_DIN) if len(value) >= 3: self.__digital_inputs_states = value[3] else: self.__digital_inputs_states = 0 return self.__digital_inputs_states @property def digital_input0(self): """ Get the state of digital input 0. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_0) @property def digital_input1(self): """ Get the state of digital input 1. A value pf 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_1) @property def digital_input2(self): """ Get the state of digital input 2. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_2) @property def digital_input3(self): """ Get the state of digital input 3. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_3) @property def digital_input4(self): """ Get the state of digital input 4. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_4) @property def digital_input5(self): """ Get the state of digital input 5. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_5) @property def digital_input6(self): """ Get the state of digital input 6. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_6) @property def digital_input7(self): """ Get the state of digital input 7. A value of 0 means 'off' and a value of 1 means 'on'. """ return self.__test_bit(self.__digital_inputs_get(), self.BIT_7) # # # ************************************************************************** # Digital Inputs short form # ************************************************************************** @property def di0(self): """ Get the state of digital input 0. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input0 <--> di0 """ return self.digital_input0 @property def di1(self): """ Get the state of digital input 1. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input1 <--> di1 """ return self.digital_input1 @property def di2(self): """ Get the state of digital input 2. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input2 <--> di2 """ return self.digital_input2 @property def di3(self): """ Get the state of digital input 3. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input3 <--> di3 """ return self.digital_input3 @property def di4(self): """ Get the state of digital input 4. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input4 <--> di4 """ return self.digital_input4 @property def di5(self): """ Get the state of digital input 5. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input5 <--> di5 """ return self.digital_input5 @property def di6(self): """ Get the state of digital input 6. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input6 <--> di6 """ return self.digital_input6 @property def di7(self): """ Get the state of digital input 7. A value of 0 means 'off' and a value of 1 means 'on'. This property is just a shorter version in terms of wording: digital_input7 <--> di7 """ return self.digital_input7 # # # ************************************************************************** # Digital Analog Converter DAC # ************************************************************************** @property def dac_selection(self): """ Get or Set the DAC selection. There are 2 DAC's on the PiXtend board. DAC A = 0 and DAC B = 1. :return: selected DAC, 0 = DAC A and 1 = DAC B :rtype: int """ return self.__analog_dac_value.bits.bit0 @dac_selection.setter def dac_selection(self, value): if value == 0 or value == 1: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 0 = DAC A or 1 = DAC B") self.__analog_dac_value.bits.bit0 = value def set_dac_output(self, value): """ Set the analog output value for the chosen DAC. The active DAC can be chosen with the property 'dac_selection'. The value 0 or constant DAC_A selects DAC A and the value 1 or constant DAC_B selects DAC B. Example: Selecting and setting DAC A: p.dac_selection = p.DAC_A p.set_dac_output (512) Selecting and setting DAC B: p.dac_selection = p.DAC_B p.set_dac_output (256) :param int value: Output value for the chosen DAC. :raises ValueError: If value is smaller then 0 or larger then 1023 """ if 0 <= value <= 1023: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: 10bit values max. 1023") analog_value = Flags16() temp_value = Flags16() dac_value = AnalogValue() analog_value.asUint16 = value # Copy the 10 relevant bits from the user var to the DAC MCP4812 data format. # The first 2 bits in the DAC 16 bit data format are unused! Therefore we start with bit2. # See the DAC manual for more details. temp_value.bits.bit2 = analog_value.bits.bit0 temp_value.bits.bit3 = analog_value.bits.bit1 temp_value.bits.bit4 = analog_value.bits.bit2 temp_value.bits.bit5 = analog_value.bits.bit3 temp_value.bits.bit6 = analog_value.bits.bit4 temp_value.bits.bit7 = analog_value.bits.bit5 temp_value.bits.bit8 = analog_value.bits.bit6 temp_value.bits.bit9 = analog_value.bits.bit7 temp_value.bits.bit10 = analog_value.bits.bit8 temp_value.bits.bit11 = analog_value.bits.bit9 # bit 15 A/B: DAC A or DAC B Selection bit temp_value.bits.bit15 = self.__analog_dac_value.bits.bit0 # bit 14 Don't care temp_value.bits.bit14 = 0 # bit 13 Output Gain Selection bit temp_value.bits.bit13 = self.__analog_dac_value.bits.bit1 # bit 12 Output Shutdown Control bit temp_value.bits.bit12 = self.__analog_dac_value.bits.bit2 # Assign the 16 bit value to another union to get the individual bytes to send via SPI dac_value.asUint16 = temp_value.asUint16 # Send the 2 bytes to the DAC via SPI self.__transfer_spi_dac_data(dac_value.bytes.byte1, dac_value.bytes.byte0) # # # ************************************************************************** # Automatic Mode for PiXtend # ************************************************************************** def auto_mode(self): """ Method for the auto(matic) mode data transfer. The settings and values of all applicable properties like outputs, GPIO and PWM configuration for the PiXtend board are sent to the microcontroller in one block and states and values of all digital and analog inputs and outputs are received as response. This is the most efficient way to work with the microcontroller on the PiXtend board. In the beginning the auto_mode method should be called until the return value is 0 and the microcontroller's uc_state is 1, meaning the communication is working and the microcontroller has entered the 'Run' state. After that the auto_mode method does not need to be called on a regular basis, but when new values are needed or outputs have to be turned on or off. Example: if p.auto_mode() == 0 and p.uc_status == 1: p.relay0 = p.ON :return: 0 means communication is ok and running, -1 means crc error and/or a problem with the received data :rtype: int """ # Set internal automatic mode to "on" (True) self.__is_automatic_mode_active = True # Set the microcontroller's control register to run in automatic mode. This value has to remain on 16 as # long as the automatic mode is used. self.__uc_ctrl.asByte = 16 # 0b00010000 # Prepare the 34 bytes array with the data to be sent to the microcontroller spi_output = [0] * 34 spi_output[0] = 128 spi_output[1] = 255 spi_output[2] = self.__digital_outputs_states spi_output[3] = self.__relays_states spi_output[4] = self.__gpio_states_auto_out spi_output[5] = self.__pwm0_value.bytes.byte0 spi_output[6] = self.__pwm0_value.bytes.byte1 spi_output[7] = self.__pwm1_value.bytes.byte0 spi_output[8] = self.__pwm1_value.bytes.byte1 spi_output[9] = self.__pwm_ctrl0.asByte spi_output[10] = self.__pwm_ctrl1.asByte spi_output[11] = self.__pwm_ctrl2.asByte spi_output[12] = self.__gpio_ctrl spi_output[13] = self.__uc_ctrl.asByte spi_output[14] = self.__ai_ctrl0.asByte spi_output[15] = self.__ai_ctrl1.asByte spi_output[16] = self.__pi_status.asByte # Calculate CRC16 Transmit Checksum crc_sum = 0xFFFF for i in range(2, 31, 1): crc_sum = self.__calc_crc16(crc_sum, spi_output[i]) spi_output[31] = crc_sum & 0xFF # CRC Low Byte spi_output[32] = (crc_sum >> 8) & 0xFF # CRC High Byte spi_output[33] = 128 # Termination # Initialize SPI Data Transfer with spi_output data spi_input = self.__transfer_spi_data(self.PIXTEND_SPI_AUTO_MODE, 0, 0, 0, spi_output) # ------------------------------------------------------------------------ # "spi_input" now contains all returned data, assign values to variables # ------------------------------------------------------------------------ # Calculate CRC16 receive checksum crc_sum = 0xFFFF for i in range(2, 31, 1): crc_sum = self.__calc_crc16(crc_sum, spi_input[i]) crc_sum_rx = (spi_input[32] << 8) + spi_input[31] if crc_sum_rx != crc_sum: # Error crc of received data and from controller are not the same return -1 else: # CRC values received and calculated -> OK self.__digital_inputs_states = spi_input[2] self.__ai0_raw_value.bytes.byte0 = spi_input[3] self.__ai0_raw_value.bytes.byte1 = spi_input[4] self.__ai1_raw_value.bytes.byte0 = spi_input[5] self.__ai1_raw_value.bytes.byte1 = spi_input[6] self.__ai2_raw_value.bytes.byte0 = spi_input[7] self.__ai2_raw_value.bytes.byte1 = spi_input[8] self.__ai3_raw_value.bytes.byte0 = spi_input[9] self.__ai3_raw_value.bytes.byte1 = spi_input[10] self.__gpio_states_auto_in = spi_input[11] self.__temp0_raw_value.bytes.byte0 = spi_input[12] self.__temp0_raw_value.bytes.byte1 = spi_input[13] self.__temp1_raw_value.bytes.byte0 = spi_input[14] self.__temp1_raw_value.bytes.byte1 = spi_input[15] self.__temp2_raw_value.bytes.byte0 = spi_input[16] self.__temp2_raw_value.bytes.byte1 = spi_input[17] self.__temp3_raw_value.bytes.byte0 = spi_input[18] self.__temp3_raw_value.bytes.byte1 = spi_input[19] self.__humid0_raw_value.bytes.byte0 = spi_input[20] self.__humid0_raw_value.bytes.byte1 = spi_input[21] self.__humid1_raw_value.bytes.byte0 = spi_input[22] self.__humid1_raw_value.bytes.byte1 = spi_input[23] self.__humid2_raw_value.bytes.byte0 = spi_input[24] self.__humid2_raw_value.bytes.byte1 = spi_input[25] self.__humid3_raw_value.bytes.byte0 = spi_input[26] self.__humid3_raw_value.bytes.byte1 = spi_input[27] self.__uc_version.UC_VERSIONL = spi_input[28] self.__uc_version.UC_VERSIONH = spi_input[29] self.__uc_status = spi_input[30] # Calculate the measured voltage from raw value to volts based on the 10 volts jumper setting if self.__ai0_jumper_setting_10_volts == 1: self.__ai0_value = c_float(self.__ai0_raw_value.asUint16 * (10.0 / 1024)) else: self.__ai0_value = c_float(self.__ai0_raw_value.asUint16 * (5.0 / 1024)) if self.__ai1_jumper_setting_10_volts == 1: self.__ai1_value = c_float(self.__ai1_raw_value.asUint16 * (10.0 / 1024)) else: self.__ai1_value = c_float(self.__ai1_raw_value.asUint16 * (5.0 / 1024)) # Calculate the milli-amps from raw value of analog inputs 2 and 3 self.__ai2_value = c_float(self.__ai2_raw_value.asUint16 * 0.024194115990990990990990990991) self.__ai3_value = c_float(self.__ai3_raw_value.asUint16 * 0.024194115990990990990990990991) self.__temp0_value = c_float(self.__temp0_raw_value.asUint16 / 10.0) self.__temp1_value = c_float(self.__temp1_raw_value.asUint16 / 10.0) self.__temp2_value = c_float(self.__temp2_raw_value.asUint16 / 10.0) self.__temp3_value = c_float(self.__temp3_raw_value.asUint16 / 10.0) self.__humid0_value = c_float(self.__humid0_raw_value.asUint16 / 10.0) self.__humid1_value = c_float(self.__humid1_raw_value.asUint16 / 10.0) self.__humid2_value = c_float(self.__humid2_raw_value.asUint16 / 10.0) self.__humid3_value = c_float(self.__humid3_raw_value.asUint16 / 10.0) return 0 @staticmethod def __calc_crc16(bycrc, bydata): """ Calculates a 16 bit CRC value. :param int bycrc: CRC value :param int bydata: data byte to be added to the CRC value :return: Calculated CRC value :rtype: int """ bycrc = bycrc ^ bydata for i in range(0, 8, 1): if bycrc & 1: bycrc = (bycrc >> 1) ^ 0xA001 else: bycrc = bycrc >> 1 return bycrc # # @property def serial_mode(self): """ Get or Set the serial mode of the PiXtend board. Use boolean values: False = RS232 and True = RS485 Example: p.serial_mode = p.RS232 # or p.serial_mode = False or p.serial_mode = p.RS485 # or p.serial_mode = True :return: Value of the serial mode :rtype: bool """ return GPIO.input(self.PIXTEND_SERIAL_PIN) @serial_mode.setter def serial_mode(self, value): if value is True or value is False: pass else: raise ValueError("Value error!, Value " + str(value) + " not allowed! - Use only: " + "False = RS232 or True = RS485") GPIO.output(self.PIXTEND_SERIAL_PIN, value) # # @staticmethod def update_rtc(): """ Update the hardware real time clock (RTC) with the current Linux system time. The system time is updated by the NTP service, which is active by default, if it is able to connect to the Internet and get the current time and date. """ subprocess.call(shlex.split("hwclock -w")) # # @staticmethod def __test_bit(int_type, offset): """ __test_bit() returns 1, if the bit at 'offset' is one else 0 if the bit is not set. :param int int_type: Integer value to test :param int offset: Offset value which bit to test :return: Integer based value of 0 if bit at 'offset' is not set or 1 if bit is set :rtype: int """ mask = 1 << offset bit_value = int_type & mask if bit_value > 0: res = 1 else: res = 0 return res @staticmethod def __set_bit(int_type, offset): """ __set_bit() returns an integer with the bit at 'offset' set to 1. :param int int_type: Integer value in which to set one bit :param int offset: Offset value which bit to set :return: Integer with bit set at 'offset' :rtype: int """ mask = 1 << offset return int_type | mask @staticmethod def __clear_bit(int_type, offset): """ __clear_bit() returns an integer with the bit at 'offset' cleared, set to 0. :param int int_type: Integer value in which to clear one bit :param int offset: Offset value which bit to clear :return: Integer with bit cleared a 'offset' :rtype: int """ mask = ~(1 << offset) return int_type & mask @staticmethod def __toggle_bit(int_type, offset): """ __toggle_bit() returns an integer with the bit at 'offset' inverted, 0 -> 1 and 1 -> 0. :param int int_type: Integer value to toggle one bit :param int offset: Offset value which bit to toggle :return: integer with bit set a 'offset' :rtype: int """ mask = 1 << offset return int_type ^ mask #