Initial commit

This commit is contained in:
2025-01-16 09:36:26 +01:00
commit 9208ff993d
5 changed files with 760 additions and 0 deletions

140
secop.py Normal file
View File

@ -0,0 +1,140 @@
import re
import os
import json
import time
import socket
from select import select
from streams import Stream, Base
IDN = re.compile('.*ISSE.*,SEC[oO]P,')
DESCRIBING = re.compile(r'describing \S* (.*)$')
UPDATE = re.compile(r'(update|error_update) (\S*) (.*)$')
class EnumConvert(dict):
def __call__(self, value):
return float(self[value])
class SecopStream(Stream):
ping_time = 0
def init(self):
self._buffer = []
print('send idn', 'tmo:', self.socket.timeout)
self.send('*IDN?')
resend = True
messages = []
for msg in self.get_lines():
if IDN.match(msg):
break
if resend:
self.send('*IDN?')
resend = False
messages.append(msg)
else:
raise ValueError(f'missing identifier, got {messages} instead')
self.send('describe')
for msg in self.get_lines():
match = DESCRIBING.match(msg)
if match:
break
else:
raise ValueError('missing describing message')
self.descr = json.loads(match.group(1))
self.device = self.descr['equipment_id']
if self.device.endswith('psi.ch'):
self.device[-6:] = []
self.tags['device'] = self.device
self.modules = self.descr['modules']
self.convert = {}
for mod, moddesc in self.modules.items():
for param, desc in moddesc['accessibles'].items():
dt = desc['datainfo']
if dt['type'] in ('double', 'int', 'enum'):
self.convert[mod, param] = float
self.send('activate')
def ping(self):
self.send('ping')
def events(self):
try:
cnt = 0
for msg in self.get_lines():
match = UPDATE.match(msg)
if match:
cmd, id, data = match.groups()
key = tuple(id.split(':'))
cvt = self.convert.get(key)
if cvt:
data = json.loads(data)
if cmd == 'error_update':
error = ': '.join(data[0:2])
print(msg, repr(error))
ts = data[2].get('t', time.time())
value = None
else:
error = None
ts = data[1].get('t', time.time())
value = cvt(data[0])
cnt += 1
yield key, value, error, ts
elif msg == 'active':
# from now on, no more waiting
self.notimeout()
#print('SECOP', self.uri, 'cnt=', cnt)
except Exception as e:
print(self.uri, repr(e))
SECOP_UDP_PORT = 10767
class UdpStream(Base):
socket = None
def events(self):
while select([self.socket], [], [], 0.5)[0]:
try:
msg, addr = self.socket.recvfrom(1024)
except socket.error: # pragma: no cover
return None
msg = json.loads(msg.decode('utf-8'))
if msg['SECoP'] != 'node':
continue
uri = f"{addr[0]}:{msg['port']}"
yield SecopStream, uri, msg['equipment_id']
class ScanReply(UdpStream):
def __init__(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# send a general broadcast
try:
sock.sendto(json.dumps(dict(SECoP='discover')).encode('utf-8'),
('255.255.255.255', SECOP_UDP_PORT))
except OSError as e:
print('could not send the broadcast:', e)
self.socket = sock
self.select_dict[sock.fileno()] = self
class ScanStream(UdpStream):
def __init__(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(1)
if os.name == 'nt':
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(('0.0.0.0', SECOP_UDP_PORT))
self.socket = sock
self.select_dict[sock.fileno()] = self