-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAmprobeSerialMeter.py
More file actions
152 lines (129 loc) · 5.49 KB
/
AmprobeSerialMeter.py
File metadata and controls
152 lines (129 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""
Driver for communications to Amprobe AM-140-A volt/multi-meter.
rx() returns float of value measured.
See PDF called 'Protocol for 500000-count multimeter series', dated 2008/12/16, 2-pages.
Checksum not implemented.
Blocking call to rx() but tx and rx can be called separately.
# pip3 install pyserial
"""
import serial
class AmprobeSerialMeter:
# command byte array. Never changes.
cmd_bar = b'\x10\x02\x00\x00\x00\x00\x10\x03'
def __init__(self):
self.port_serial = None
self.rsp_bar = b''
self.val = float('nan')
def open(self, port):
# open serial port
self.port_serial = serial.Serial(
port=port,
baudrate=9600,
parity=serial.PARITY_NONE,
timeout=0.2,
inter_byte_timeout=0.1)
#print('{0} is open.'.format(self.port_serial.name)) # check which port was really used
def close(self):
self.port_serial.close()
def tx(self, cmd_bar=cmd_bar):
if self.port_serial is None or not self.port_serial.is_open:
return
self.port_serial.flush()
self.port_serial.write(cmd_bar)
def rx(self) -> float:
if self.port_serial is None or not self.port_serial.is_open:
self.rsp_bar = b''
else:
self.rsp_bar = self.port_serial.read_until(b'\x10\x03') # response ends with '\x10\x02'
# print(rsp_bar)
if len(self.rsp_bar) == 14 and self.rsp_bar[0:4] == b'\x10\x02\x01\x07' and self.rsp_bar[9:11] == b'OL': # if
self.val = float('inf')
elif len(self.rsp_bar) == 0:
# This occurs when the range changes.
# Keep old value as-is
self.val = self.val
elif len(self.rsp_bar) == 22 and self.rsp_bar[0:4] == b'\x10\x02\x00\x0F':
# Expect number, in scientific notation, in bytes 8 thru 18 inclusive.
# remove the space before the E.
s = bytearray(self.rsp_bar[8:19]).decode('ascii').replace(' E', 'E')
# At this point, s is expected to be a number of the form -0.0006E-1.
# Convert to float.
try:
self.val = float(s)
except ValueError:
self.val = 0
else:
self.val = float('nan')
return self.val
# From table 1 in 'Protocol for 500000-count multimeter series'
def get_range(self) -> str:
if len(self.rsp_bar) == 0:
return '?'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00000101:
return 'AcV'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00000110:
return 'DcV'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00000111:
return 'AC+DCV'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00001000:
return 'Cx'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00010100:
return 'Dx'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00100000:
return '°C'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b01000000:
return '°F'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b10000000:
return 'Ohm'
elif self.rsp_bar[5] == 0b00000001 and self.rsp_bar[4] == 0b10000000:
return 'Conti' # Beep mode.
elif self.rsp_bar[5] == 0b00000010 and self.rsp_bar[4] == 0b00000001:
return 'AcA'
elif self.rsp_bar[5] == 0b00000010 and self.rsp_bar[4] == 0b00000010:
return 'DcA'
elif self.rsp_bar[5] == 0b00000010 and self.rsp_bar[4] == 0b00000011:
return 'Ac+DcA'
elif self.rsp_bar[5] == 0b00000100 and self.rsp_bar[4] == 0b00000000:
return 'Hz'
elif self.rsp_bar[5] == 0b00001000 and self.rsp_bar[4] == 0b00000000:
return 'Duty%'
elif self.rsp_bar[5] == 0b00100000 and self.rsp_bar[4] == 0b00000000:
return 'dB'
elif self.rsp_bar[5] == 0b00000000 and self.rsp_bar[4] == 0b00000100: # not documented but condition seen.
return 'Diode'
else:
return '?'
# test case, hard coded for a COM port. Reads forever.
if __name__ == '__main__':
import time
import EngineeringNotation
import os
com_port_name = 'COM6'
uut = AmprobeSerialMeter()
try:
uut.open(com_port_name)
except serial.serialutil.SerialException as e:
msg = None
if os.name == 'nt':
# When unplugged:
# could not open port 'COM6': FileNotFoundError(2, 'The system cannot find the file specified.', None, 2)
if str(e).find('FileNotFoundError') > -1:
msg = com_port_name + ' not found'
# When already open:
# could not open port 'COM6': PermissionError(13, 'Access is denied.', None, 5)
elif str(e).find('PermissionError') > -1:
msg = com_port_name + ' might already be open'
if msg is None:
msg = str(e)
print(msg)
exit(-1)
time.sleep(0.2)
uut.tx()
time.sleep(0.2)
uut.tx()
while True:
f = uut.rx()
# print float, response string and range.
print('{0} ({1}) {2}'.format(EngineeringNotation.to_string(f, 3), uut.rsp_bar, uut.get_range()))
uut.tx()
time.sleep(1)