Initial commit
This commit is contained in:
commit
dc4c035a53
11 changed files with 305 additions and 0 deletions
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
31
README.md
Normal file
31
README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
Создаем пару виртуальных портов:
|
||||||
|
|
||||||
|
$ socat -d -d pty,rawer,echo=0 pty,rawer,echo=0
|
||||||
|
2023/10/01 03:18:39 socat[965194] N PTY is /dev/pts/16
|
||||||
|
2023/10/01 03:18:39 socat[965194] N PTY is /dev/pts/19
|
||||||
|
|
||||||
|
Запускаем писателя на другой консоли:
|
||||||
|
|
||||||
|
$ ./writer.py
|
||||||
|
Write: GET_A
|
||||||
|
Write: GET_B
|
||||||
|
Write: GET_C
|
||||||
|
Write: GET_NORESP
|
||||||
|
Write: INVALID_CMD
|
||||||
|
...
|
||||||
|
|
||||||
|
На следующей консоли за пускаем парсер:
|
||||||
|
|
||||||
|
$ ./reader.py
|
||||||
|
01-10-2023 15:17:07 DEBUG:read: GET_B
|
||||||
|
01-10-2023 15:17:07 INFO:response_str: B_5V
|
||||||
|
01-10-2023 15:17:09 DEBUG:read: GET_C
|
||||||
|
01-10-2023 15:17:09 INFO:response_str: C_15A
|
||||||
|
01-10-2023 15:17:11 DEBUG:read: GET_NORESP
|
||||||
|
01-10-2023 15:17:11 WARNING:No value for sensor: NORESP
|
||||||
|
01-10-2023 15:17:13 DEBUG:read: INVALID_CMD
|
||||||
|
01-10-2023 15:17:13 WARNING:Command parser error: Unknown command: INVALID
|
||||||
|
01-10-2023 15:17:15 DEBUG:read: GET_A
|
||||||
|
...
|
||||||
|
|
||||||
|
|
13
command_parser/__init__.py
Normal file
13
command_parser/__init__.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
class CommandParser:
|
||||||
|
@staticmethod
|
||||||
|
def parse(command_string):
|
||||||
|
parsed_command = command_string.split('_')
|
||||||
|
|
||||||
|
command = parsed_command[0]
|
||||||
|
value = parsed_command[1]
|
||||||
|
|
||||||
|
match command:
|
||||||
|
case 'GET':
|
||||||
|
return {'command': command, 'value': value}
|
||||||
|
case _:
|
||||||
|
raise ValueError("Unknown command: "+command)
|
20
config.yml
Normal file
20
config.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#transport:
|
||||||
|
# type: serial
|
||||||
|
# tty_name: "/dev/pts/19"
|
||||||
|
#
|
||||||
|
#writer:
|
||||||
|
# transport:
|
||||||
|
# type: serial
|
||||||
|
# tty_name: "/dev/pts/16"
|
||||||
|
|
||||||
|
|
||||||
|
transport:
|
||||||
|
type: tcp
|
||||||
|
host: "127.0.0.1"
|
||||||
|
port: 8889
|
||||||
|
|
||||||
|
writer:
|
||||||
|
transport:
|
||||||
|
type: tcp
|
||||||
|
host: "127.0.0.1"
|
||||||
|
port: 8889
|
67
data_processor/__init__.py
Normal file
67
data_processor/__init__.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import importlib
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from command_parser import CommandParser
|
||||||
|
|
||||||
|
# В идеале, тут должен быть какой-то класс собирающий данные, но не вижу смысла увеличивать количество кода сейчас
|
||||||
|
response_data = {
|
||||||
|
'A': '10V',
|
||||||
|
'B': '5V',
|
||||||
|
'C': '15A'
|
||||||
|
}
|
||||||
|
|
||||||
|
response_template = '{sensor}_{value}'
|
||||||
|
|
||||||
|
logging.basicConfig(format='%(asctime)s %(levelname)s:%(message)s', datefmt='%d-%m-%Y %H:%M:%S', level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class DataProcessor:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
try:
|
||||||
|
package = 'transport.{type}'
|
||||||
|
full_package_name = package.format(type = self.config["transport"]["type"])
|
||||||
|
cls = getattr(importlib.import_module(full_package_name), 'Transport')
|
||||||
|
|
||||||
|
self.transport = cls(self.config["transport"])
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
print("Can't create transport: ", err)
|
||||||
|
# raise err
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
logging.debug("Waiting for commands...")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
command_string = self.transport.readline()
|
||||||
|
logging.debug('read: %s', command_string)
|
||||||
|
|
||||||
|
try:
|
||||||
|
command = CommandParser.parse(command_string)
|
||||||
|
except ValueError as err:
|
||||||
|
logging.warning("Command parser error: %s", err)
|
||||||
|
self.transport.writeline('Failed to process command!')
|
||||||
|
else:
|
||||||
|
parsed_command_name = command.get('command')
|
||||||
|
|
||||||
|
response_str = None
|
||||||
|
match parsed_command_name:
|
||||||
|
case 'GET':
|
||||||
|
sensor_name = command.get('value')
|
||||||
|
response_value = response_data.get(sensor_name)
|
||||||
|
|
||||||
|
if bool(response_value):
|
||||||
|
response_str = response_template.format(sensor=command['value'], value=response_value)
|
||||||
|
logging.info('response_str: %s', response_str)
|
||||||
|
|
||||||
|
else:
|
||||||
|
response_str = '00'
|
||||||
|
logging.warning('No value for sensor: %s', sensor_name)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
print('Unknown command: %s', parsed_command_name)
|
||||||
|
response_str = 'Unknown command'
|
||||||
|
|
||||||
|
self.transport.writeline(response_str)
|
31
reader.py
Normal file
31
reader.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import getopt
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from data_processor import DataProcessor
|
||||||
|
|
||||||
|
config_name = 'config.yml'
|
||||||
|
|
||||||
|
options = "hc:"
|
||||||
|
long_options = ["help", "config="]
|
||||||
|
|
||||||
|
argumentList = sys.argv[1:]
|
||||||
|
arguments, values = getopt.getopt(argumentList, options, long_options)
|
||||||
|
|
||||||
|
for currentArgument, currentValue in arguments:
|
||||||
|
|
||||||
|
if currentArgument in ("-h", "--help"):
|
||||||
|
print("reader.py [--config=path/to/config.yml]")
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
elif currentArgument in ("-c", "--config"):
|
||||||
|
config_name = currentValue
|
||||||
|
|
||||||
|
with open(config_name) as f:
|
||||||
|
config = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
print("config: ", config)
|
||||||
|
|
||||||
|
processor = DataProcessor(config)
|
||||||
|
processor.run()
|
12
transport/base/__init__.py
Normal file
12
transport/base/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
class TransportBase:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def writeline(self, cmd_str):
|
||||||
|
raise Exception('Implement me!')
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
raise Exception('Implement me!')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
raise Exception('Implement me!')
|
28
transport/serial/__init__.py
Normal file
28
transport/serial/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
import serial
|
||||||
|
|
||||||
|
from transport.base import TransportBase
|
||||||
|
|
||||||
|
|
||||||
|
class Transport(TransportBase):
|
||||||
|
def __init__(self, config):
|
||||||
|
super().__init__(config)
|
||||||
|
self.ser = serial.Serial()
|
||||||
|
self.ser.port = config["tty_name"]
|
||||||
|
|
||||||
|
self.ser.open()
|
||||||
|
self.ser.flushInput()
|
||||||
|
self.ser.flushOutput()
|
||||||
|
|
||||||
|
self.addr = None
|
||||||
|
|
||||||
|
def writeline(self, cmd_str):
|
||||||
|
self.ser.write(str.encode(cmd_str + "\n"))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
buffer = self.ser.readline()
|
||||||
|
return buffer.decode('utf-8').rstrip()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.ser.close()
|
51
transport/tcp/__init__.py
Normal file
51
transport/tcp/__init__.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from transport.base import TransportBase
|
||||||
|
|
||||||
|
|
||||||
|
class Transport(TransportBase):
|
||||||
|
def __init__(self, config):
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
|
self.listener = self.__connect(config['host'], config['port'])
|
||||||
|
self.buffer = bytearray()
|
||||||
|
|
||||||
|
def writeline(self, cmd_str):
|
||||||
|
response_string = cmd_str + "\n"
|
||||||
|
self.conn.sendall(bytes(response_string, 'utf-8'))
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
data = self.conn.recv(16) # команды все короткие, нет смысла много читать из буфера
|
||||||
|
# print('data: ', data)
|
||||||
|
if not data:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
line = self.__findLine(data)
|
||||||
|
|
||||||
|
return line
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.listener.close()
|
||||||
|
|
||||||
|
def __connect(self, host='127.0.0.1', port=12345):
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as socket_listener:
|
||||||
|
socket_listener.bind((host, port))
|
||||||
|
socket_listener.listen()
|
||||||
|
|
||||||
|
conn, addr = socket_listener.accept()
|
||||||
|
self.conn = conn
|
||||||
|
return socket_listener
|
||||||
|
|
||||||
|
def __findLine(self, data):
|
||||||
|
data_chunk = bytearray(self.buffer)
|
||||||
|
data_chunk.extend(data)
|
||||||
|
|
||||||
|
# print("line: ", data_chunk)
|
||||||
|
line_separator_index = data_chunk.find(b"\n")
|
||||||
|
# print('n idx: ', line_separator_index)
|
||||||
|
|
||||||
|
if line_separator_index > 0:
|
||||||
|
line = data_chunk[0: line_separator_index]
|
||||||
|
self.buffer = data_chunk[line_separator_index+1:]
|
||||||
|
|
||||||
|
return line.decode('utf-8')
|
24
writer.py
Normal file
24
writer.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from transport.serial import Transport
|
||||||
|
|
||||||
|
config_name = "config.yml"
|
||||||
|
|
||||||
|
with open(config_name) as f:
|
||||||
|
config = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
|
connector = Transport(config["writer"]["transport"])
|
||||||
|
|
||||||
|
commands_list = ['GET_A', 'GET_B', 'GET_C', 'GET_NORESP', 'INVALID_CMD']
|
||||||
|
|
||||||
|
while (True):
|
||||||
|
for cmd in commands_list:
|
||||||
|
print('Write: ' + cmd)
|
||||||
|
connector.writeline(cmd)
|
||||||
|
|
||||||
|
sleep(0.5)
|
||||||
|
print("Response: "+connector.readline())
|
||||||
|
sleep(1)
|
25
writer_tcp.py
Normal file
25
writer_tcp.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import socket
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
config_name = "config.yml"
|
||||||
|
|
||||||
|
with open(config_name) as f:
|
||||||
|
config = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
|
||||||
|
commands_list = ['GET_A', 'GET_B', 'GET_C', 'GET_NORESP', 'INVALID_CMD']
|
||||||
|
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
s.connect((config["writer"]["transport"]["host"], config["writer"]["transport"]["port"]))
|
||||||
|
|
||||||
|
while (True):
|
||||||
|
for cmd in commands_list:
|
||||||
|
print('Write: ' + cmd)
|
||||||
|
s.sendall(bytes(cmd+"\n", 'utf-8'))
|
||||||
|
# data = s.recv(1024)
|
||||||
|
|
||||||
|
sleep(0.5)
|
||||||
|
# print("Response: ", data.decode('utf-8'))
|
||||||
|
sleep(1)
|
Loading…
Reference in a new issue