-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinput_data_validation.py
More file actions
145 lines (118 loc) · 4.97 KB
/
input_data_validation.py
File metadata and controls
145 lines (118 loc) · 4.97 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
from typing import Optional
import re
# allowed column names in csv file or pandas data frame.
# Noie: any input names will be stripped, lower cased and '_' removed to be robust against typos of user inputs
csv_column_names: list[str] = ['registerstart', 'registerend', 'name', 'registertype', 'datatype', 'unit', 'scaling',
'unitid', 'description']
# List of possible data types:
data_types = (('int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float16', 'float32', 'float64', 'bool')
+ tuple(f'string{i}' for i in range(129)))
# List of aliases for data types
# Note: any input names will be lower cased to be robust against typos of user inputs
# Note: as a modbus register is 16 bit long, we use the term 'int' for a 16 bit variable
data_type_lookup: dict[str, str] = {
'int': 'int16', 'short': 'int16', 'int16': 'int16', 's16': 'int16',
'uint': 'uint16', 'ushort': 'uint16', 'uint16': 'uint16', 'u16': 'uint16',
'dint': 'int32', 'long': 'int32', 'int32': 'int32', 's32': 'int32',
'ulong': 'uint32', 'uint32': 'uint32', 'u32': 'uint32',
'int64': 'int64', 's64': 'int64',
'uint64': 'uint64', 'u64': 'uint64',
'half': 'float16', 'float16': 'float16',
'float': 'float32', 'single': 'float32', 'real': 'float32', 'float32': 'float32',
'double': 'float64', 'float64': 'float64',
'bool': 'bool', 'bit': 'bool', 'boolean': 'bool', 'coil': 'bool',
**{f'string{i}': f'string{i}' for i in range(129)}
}
# List of possible register types
register_types = ('i', 'h', 'c', 'd')
# List of aliases for register types.
# Note: any input names will be lower cased to be robust against typos of user inputs
register_type_lookup: dict[str, str] = {
'i': 'i', '4': 'i', '0x04': 'i', 'ir': 'i', 'inputregister': 'i', 'inputreg': 'i',
'h': 'h', '3': 'h', '0x03': 'h', 'hr': 'h', 'holdingregister': 'h', 'holdingreg': 'h',
'c': 'c', '1': 'c', '0x01': 'c', 'co': 'c', 'coils': 'c',
'd': 'd', '2': 'd', '0x02': 'd', 'di': 'd', 'discreteinput': 'd',
}
# Can this register be read or written?
mode_types = ('r', 'w', 'rw')
def check_used(used) -> bool:
if used is None:
# column is empty which will be interpreted as True since the column is optional
return True
if isinstance(used, bool):
return used
if isinstance(used, int):
return bool(int)
if isinstance(used, str):
used = used.lower().strip()
if len(used) == 0:
return True # empty string which will be interpreted as True since the column is optional
valid = {'true': True, 't': True, '1': True, 'false': False, 'f': False, '0': False}
try:
return valid[used]
except KeyError:
raise ValueError(f'Cannot interpret string {used} as True or False value')
raise ValueError(f'Cannot handle parameter {used} with type {type(used)}')
def check_register_type(register_type: str) -> str:
"""
check for valid register type names by looking up in a dict of aliases (c.f. dict register_type_lookup)
'_' and case will be ignored
:param register_type:
:return:
"""
try:
return register_type_lookup[register_type.replace('_', '').lower()]
except KeyError:
raise ValueError(f'Invalid register type: {register_type}')
def check_data_type(data_type: str) -> str:
"""
check for valid data type names by looking up in a dict of aliases (c.f. dict data_type_lookup)
'_' and case will be ignored
:param data_type:
:return:
"""
normalized = data_type.replace('_', '').lower().strip()
# Check if it matches the pattern for a string type (e.g. "string12")
match = re.fullmatch(r'string(\d+)', normalized)
if match:
length = int(match.group(1))
if length < 1:
raise ValueError(f"Invalid register type: {data_type}")
return f"string{length}" # Return in the form "string<number>"
try:
return data_type_lookup[normalized]
except KeyError:
raise ValueError(f'Invalid register type: {data_type}')
def check_optional_string(value: Optional[str]) -> str:
"""
a None will result in an empty string, otherwise the input is stripped and passed
:param value:
:return:
"""
if value is None:
value = ''
return value.strip()
def check_scaling(scaling: Optional[str]) -> float | None:
"""
None or empty string will result in 1.0, all other values will be parsed to float
:param scaling:
:return:
"""
if scaling is None or scaling == '':
return None
try:
return float(scaling.strip())
except ValueError:
return 1.0
def check_mode(mode: Optional[str]) -> str:
"""
None or empty string will result in 'r', all other values will be stripped
:param mode: Input mode
:return: Corrected mode
"""
if mode is None or mode == '':
return 'r'
if mode.strip().lower() in mode_types:
return mode
else:
raise ValueError(f'Invalid mode: {mode}')