Skip to content

Commit

Permalink
Fix error handling to be more platform independent
Browse files Browse the repository at this point in the history
  • Loading branch information
MC325 committed Jul 23, 2024
1 parent ff9e7aa commit 1552f26
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 41 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ To check if a controller is connected, call
controller.is_connected()
```

For the majority of users running the library on Linux, the designated Ethernet port is 'eth0'. In cases where a different Ethernet port is utilized to connect with the controller, modify the ETHERNET_PORT constant.
For example, to switch the Ethernet port to 'eth1':
```python
instec.connection.ETHERNET_PORT = 'eth1'
```

### Functions

All functions in instec.py are instance methods, meaning they must be called with an instance of the controller. For example,
Expand Down
5 changes: 3 additions & 2 deletions instec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"""

from instec.instec import MK2000
from instec.constants import (mode, system_status, temperature_mode, unit,
profile_status, profile_item, pid_table, connection)
from instec.constants import (mode, system_status, temperature_mode,
unit, profile_status, profile_item,
pid_table, connection)
10 changes: 6 additions & 4 deletions instec/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
class command:
def get_ethernet_controllers():
return controller.get_ethernet_controllers()

def get_usb_controllers():
return controller.get_usb_controllers()

def __init__(self, conn_mode: mode = None,
baudrate: int = 38400, port: str = None, serial_num: str = None, ip: str = None):
baudrate: int = 38400, port: str = None,
serial_num: str = None, ip: str = None):
"""Initialize any relevant attributes necessary to connect to the
controller, and define the connection mode.
Expand All @@ -25,7 +26,8 @@ def __init__(self, conn_mode: mode = None,
port (str, optional): Serial port (for USB only).
Defaults to None.
"""
self._controller = controller(conn_mode, baudrate, port, serial_num, ip)
self._controller = controller(conn_mode, baudrate,
port, serial_num, ip)

def connect(self):
"""Connect to controller via selected connection mode.
Expand Down
2 changes: 2 additions & 0 deletions instec/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

from enum import Enum


class connection:
TIMEOUT = 1
ETHERNET_PORT = 'eth0'
IP_ADDRESS = None


class mode(Enum):
"""Enums for connection mode.
"""
Expand Down
68 changes: 37 additions & 31 deletions instec/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
from serial.tools import list_ports
import socket
import sys
if sys.platform.startswith('linux'): import fcntl, struct
if sys.platform.startswith('linux'):
import fcntl
import struct
from instec.constants import mode, connection


class udp_singleton:
def __new__(cls):
if not hasattr(cls, 'instance'):
Expand All @@ -17,13 +20,14 @@ def __new__(cls):
cls.receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cls.receiver.bind(
((udp_singleton._get_eth_addr()
), 50291))
), 50291))
cls.receiver.settimeout(connection.TIMEOUT)
if not hasattr(cls, 'sender'):
cls.sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cls.sender.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
cls.sender.setsockopt(
socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
return cls.instance

def _get_eth_addr():
if isinstance(connection.IP_ADDRESS, str):
return connection.IP_ADDRESS
Expand All @@ -36,27 +40,30 @@ def _get_eth_addr():
fcntl.ioctl(
s.fileno(),
0x8915,
struct.pack('256s', connection.ETHERNET_PORT.encode()))[20:24])
struct.pack('256s',
connection.ETHERNET_PORT.encode()
))[20:24])
except Exception as error:
raise RuntimeError('Issue finding ethernet port') from error
else:
raise RuntimeError('Unsupported OS')


class controller:
"""All basic communication functions to interface with the MK2000/MK2000B.
"""
udp = udp_singleton()
ethernet = []
usb = []

def get_ethernet_controllers():
"""Get all controllers connected via Ethernet.
Returns:
List: List of tuples in the form (serial_num, ip)
"""


controller.udp = udp_singleton()

controller.udp.sender.sendto(
bytes.fromhex('73C4000001'),
('255.255.255.255', 50290))
Expand All @@ -71,11 +78,11 @@ def get_ethernet_controllers():
serial_num = data[1]
if model.startswith('IoT_MK#MK2000'):
controller.ethernet.append((serial_num, addr[0]))
except socket.error as error:
except socket.error:
break
except Exception as error:
raise RuntimeError('Did not receive UDP response') from error

return controller.ethernet

def get_usb_controllers():
Expand All @@ -91,21 +98,21 @@ def get_usb_controllers():
conn.timeout = connection.TIMEOUT
try:
conn.open()

conn.write(str.encode('TEMP:SNUM?'))

buffer = conn.readline().decode()
while not buffer.endswith('\r\n'):
buffer += conn.readline().decode()

data = buffer.strip().split(',')
company = data[0]
model = data[1]
serial_num = data[2]

if company == 'Instec' and model.startswith('MK2000'):
controller.usb.append((serial_num, port))

except Exception:
continue

Expand All @@ -121,9 +128,10 @@ def _get_controller_by_serial_number(self, serial_num: str):
ValueError: If a controller with the serial number is not found.
Returns:
List: List of tuples in the form (serial_num, param). where param is
either the port (USB) or IP address (Ethernet)
List: List of tuples in the form (serial_num, param). where param
is either the port (USB) or IP address (Ethernet)
"""

if self._mode in [mode.USB, None]:
try:
for c in controller.usb:
Expand All @@ -148,10 +156,12 @@ def _get_controller_by_serial_number(self, serial_num: str):
return c[1]
except TypeError:
pass
raise ValueError(f'Controller with serial number {serial_num} not connected.')
raise ValueError(f'Controller with serial number'
f'{serial_num} not connected.')

def __init__(self, conn_mode: mode = None,
baudrate: int = 38400, port: str = None, serial_num: str = None, ip: str = None):
baudrate: int = 38400, port: str = None,
serial_num: str = None, ip: str = None):
"""Initialize any relevant attributes necessary to connect to the
controller, and define the connection mode.
Expand All @@ -164,7 +174,8 @@ def __init__(self, conn_mode: mode = None,
Defaults to None.
serial_num (str, optional): Serial number of controller.
Defaults to None.
ip (str, optional): IP address of controller (Ethernet mode only).
ip (str, optional): IP address of controller
(Ethernet mode only).
Defaults to None.
Raises:
Expand Down Expand Up @@ -195,7 +206,6 @@ def __init__(self, conn_mode: mode = None,
raise ValueError('Invalid connection mode')

def connect(self):

"""Connect to controller via selected connection mode.
Raises:
Expand All @@ -215,12 +225,10 @@ def connect(self):
socket.AF_INET,
socket.SOCK_STREAM)
self._tcp_socket.settimeout(10)

try:
self._tcp_socket.connect((self._controller_address, 50292))
except OSError as error:
if error.winerror == 10054:
self._tcp_socket.connect((self._controller_address, 50292))
raise RuntimeError('Issues connecting to device') from error
except Exception as error:
raise RuntimeError('Unable to establish '
'TCP connection') from error
Expand Down Expand Up @@ -268,19 +276,17 @@ def is_connected(self):
return True
except ConnectionResetError:
return False
except OSError as error:
if sys.platform == 'win32' and error.winerror == 10038:
return False
except OSError:
return False
except ValueError:
return False
except Exception as error:
raise RuntimeError('Unknown exception occured') from error
finally:
try:
self._tcp_socket.settimeout(timeout)
except OSError as error:
if sys.platform == "win32" and error.winerror == 10038:
pass
except OSError:
pass
except AttributeError:
return False
else:
Expand Down
6 changes: 3 additions & 3 deletions instec/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ def hold(self, tsp: float):
raise ValueError('Set point value is out of range')
else:
raise ValueError('Set point value is out of range')
def _hold_no_check(self, tsp:float):

def _hold_no_check(self, tsp: float):
"""Same as normal hold function, but has no error check. This function
should not be used unless latency is crucial.
Expand Down Expand Up @@ -200,7 +200,7 @@ def rpp(self, pp: float):
self._controller._send_command(f'TEMP:RPP {float(pp)}', False)
else:
raise ValueError('Power percentage is out of range')

def _rpp_no_check(self, pp: float):
"""Same as normal rpp function, but has no error check. This function
should not be used unless latency is crucial.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "instec"
version = "1.1.62"
version = "1.1.63"
authors = [
{ name="Matthew Chen", email="matthew.chen@instec.com" },
]
Expand Down

0 comments on commit 1552f26

Please sign in to comment.