mirror of
https://github.com/tomaae/homeassistant-mikrotik_router.git
synced 2025-07-14 11:24:31 +02:00
reverted to official librouteros #9
This commit is contained in:
parent
25e90702d2
commit
3cf637d1ba
9 changed files with 31 additions and 621 deletions
|
@ -1,60 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
from socket import create_connection
|
|
||||||
from collections import ChainMap
|
|
||||||
|
|
||||||
from .exceptions import (
|
|
||||||
ConnectionClosed,
|
|
||||||
FatalError,
|
|
||||||
)
|
|
||||||
from .connections import SocketTransport
|
|
||||||
from .protocol import ApiProtocol
|
|
||||||
from .login import (
|
|
||||||
plain,
|
|
||||||
token,
|
|
||||||
)
|
|
||||||
from .api import Api
|
|
||||||
|
|
||||||
DEFAULTS = {
|
|
||||||
'timeout': 10,
|
|
||||||
'port': 8728,
|
|
||||||
'saddr': '',
|
|
||||||
'subclass': Api,
|
|
||||||
'encoding': 'ASCII',
|
|
||||||
'ssl_wrapper': lambda sock: sock,
|
|
||||||
'login_method': plain,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def connect(host, username, password, **kwargs):
|
|
||||||
"""
|
|
||||||
Connect and login to routeros device.
|
|
||||||
Upon success return a Api class.
|
|
||||||
|
|
||||||
:param host: Hostname to connecto to. May be ipv4,ipv6,FQDN.
|
|
||||||
:param username: Username to login with.
|
|
||||||
:param password: Password to login with. Only ASCII characters allowed.
|
|
||||||
:param timeout: Socket timeout. Defaults to 10.
|
|
||||||
:param port: Destination port to be used. Defaults to 8728.
|
|
||||||
:param saddr: Source address to bind to.
|
|
||||||
:param subclass: Subclass of Api class. Defaults to Api class from library.
|
|
||||||
:param ssl_wrapper: Callable (e.g. ssl.SSLContext instance) to wrap socket with.
|
|
||||||
:param login_method: Callable with login method.
|
|
||||||
"""
|
|
||||||
arguments = ChainMap(kwargs, DEFAULTS)
|
|
||||||
transport = create_transport(host, **arguments)
|
|
||||||
protocol = ApiProtocol(transport=transport, encoding=arguments['encoding'])
|
|
||||||
api = arguments['subclass'](protocol=protocol)
|
|
||||||
|
|
||||||
try:
|
|
||||||
arguments['login_method'](api=api, username=username, password=password)
|
|
||||||
return api
|
|
||||||
except (ConnectionClosed, FatalError):
|
|
||||||
transport.close()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def create_transport(host, **kwargs):
|
|
||||||
sock = create_connection((host, kwargs['port']), kwargs['timeout'], (kwargs['saddr'], 0))
|
|
||||||
sock = kwargs['ssl_wrapper'](sock)
|
|
||||||
return SocketTransport(sock=sock)
|
|
|
@ -1,136 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
from posixpath import join as pjoin
|
|
||||||
|
|
||||||
from .exceptions import TrapError, MultiTrapError
|
|
||||||
from .protocol import (
|
|
||||||
compose_word,
|
|
||||||
parse_word,
|
|
||||||
)
|
|
||||||
from .query import Query
|
|
||||||
|
|
||||||
|
|
||||||
class Api:
|
|
||||||
|
|
||||||
def __init__(self, protocol):
|
|
||||||
self.protocol = protocol
|
|
||||||
|
|
||||||
def __call__(self, cmd, **kwargs):
|
|
||||||
"""
|
|
||||||
Call Api with given command.
|
|
||||||
Yield each row.
|
|
||||||
|
|
||||||
:param cmd: Command word. eg. /ip/address/print
|
|
||||||
:param kwargs: Dictionary with optional arguments.
|
|
||||||
"""
|
|
||||||
words = (compose_word(key, value) for key, value in kwargs.items())
|
|
||||||
self.protocol.writeSentence(cmd, *words)
|
|
||||||
yield from self.readResponse()
|
|
||||||
|
|
||||||
def rawCmd(self, cmd, *words):
|
|
||||||
"""
|
|
||||||
Call Api with given command and raw words.
|
|
||||||
End user is responsible to properly format each api word argument.
|
|
||||||
:param cmd: Command word. eg. /ip/address/print
|
|
||||||
:param args: Iterable with optional plain api arguments.
|
|
||||||
"""
|
|
||||||
self.protocol.writeSentence(cmd, *words)
|
|
||||||
yield from self.readResponse()
|
|
||||||
|
|
||||||
def readSentence(self):
|
|
||||||
"""
|
|
||||||
Read one sentence and parse words.
|
|
||||||
|
|
||||||
:returns: Reply word, dict with attribute words.
|
|
||||||
"""
|
|
||||||
reply_word, words = self.protocol.readSentence()
|
|
||||||
words = dict(parse_word(word) for word in words)
|
|
||||||
return reply_word, words
|
|
||||||
|
|
||||||
def readResponse(self):
|
|
||||||
"""
|
|
||||||
Yield each sentence untill !done is received.
|
|
||||||
|
|
||||||
:throws TrapError: If one !trap is received.
|
|
||||||
:throws MultiTrapError: If > 1 !trap is received.
|
|
||||||
"""
|
|
||||||
traps = []
|
|
||||||
reply_word = None
|
|
||||||
while reply_word != '!done':
|
|
||||||
reply_word, words = self.readSentence()
|
|
||||||
if reply_word == '!trap':
|
|
||||||
traps.append(TrapError(**words))
|
|
||||||
elif reply_word in ('!re', '!done') and words:
|
|
||||||
yield words
|
|
||||||
|
|
||||||
if len(traps) > 1:
|
|
||||||
raise MultiTrapError(*traps)
|
|
||||||
if len(traps) == 1:
|
|
||||||
raise traps[0]
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.protocol.close()
|
|
||||||
|
|
||||||
def path(self, *path):
|
|
||||||
return Path(
|
|
||||||
path='',
|
|
||||||
api=self,
|
|
||||||
).join(*path)
|
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
|
||||||
"""Represents absolute command path."""
|
|
||||||
|
|
||||||
def __init__(self, path, api):
|
|
||||||
self.path = path
|
|
||||||
self.api = api
|
|
||||||
|
|
||||||
def select(self, key, *other):
|
|
||||||
keys = (key, ) + other
|
|
||||||
return Query(path=self, keys=keys, api=self.api)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.path
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<{module}.{cls} {path!r}>".format(
|
|
||||||
module=self.__class__.__module__,
|
|
||||||
cls=self.__class__.__name__,
|
|
||||||
path=self.path,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield from self('print')
|
|
||||||
|
|
||||||
def __call__(self, cmd, **kwargs):
|
|
||||||
yield from self.api(
|
|
||||||
self.join(cmd).path,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
def join(self, *path):
|
|
||||||
"""Join current path with one or more path strings."""
|
|
||||||
return Path(
|
|
||||||
api=self.api,
|
|
||||||
path=pjoin('/', self.path, *path).rstrip('/'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def remove(self, *ids):
|
|
||||||
ids = ','.join(ids)
|
|
||||||
tuple(self(
|
|
||||||
'remove',
|
|
||||||
**{'.id': ids},
|
|
||||||
))
|
|
||||||
|
|
||||||
def add(self, **kwargs):
|
|
||||||
ret = self(
|
|
||||||
'add',
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
return tuple(ret)[0]['ret']
|
|
||||||
|
|
||||||
def update(self, **kwargs):
|
|
||||||
tuple(self(
|
|
||||||
'set',
|
|
||||||
**kwargs,
|
|
||||||
))
|
|
|
@ -1,37 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
from .exceptions import ConnectionClosed
|
|
||||||
|
|
||||||
|
|
||||||
class SocketTransport:
|
|
||||||
|
|
||||||
def __init__(self, sock):
|
|
||||||
self.sock = sock
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
"""
|
|
||||||
Write given bytes to socket. Loop as long as every byte in
|
|
||||||
string is written unless exception is raised.
|
|
||||||
"""
|
|
||||||
self.sock.sendall(data)
|
|
||||||
|
|
||||||
def read(self, length):
|
|
||||||
"""
|
|
||||||
Read as many bytes from socket as specified in length.
|
|
||||||
Loop as long as every byte is read unless exception is raised.
|
|
||||||
"""
|
|
||||||
data = bytearray()
|
|
||||||
while len(data) != length:
|
|
||||||
tmp = None
|
|
||||||
try:
|
|
||||||
tmp = self.sock.recv((length - len(data)))
|
|
||||||
except:
|
|
||||||
raise ConnectionClosed('Socket recv failed.')
|
|
||||||
|
|
||||||
data += tmp
|
|
||||||
if not data:
|
|
||||||
raise ConnectionClosed('Connection unexpectedly closed.')
|
|
||||||
return data
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.sock.close()
|
|
|
@ -1,52 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
class LibRouterosError(Exception):
|
|
||||||
"""Base exception for all other."""
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionClosed(LibRouterosError):
|
|
||||||
"""Raised when connection have been closed."""
|
|
||||||
|
|
||||||
|
|
||||||
class ProtocolError(LibRouterosError):
|
|
||||||
"""Raised when e.g. encoding/decoding fails."""
|
|
||||||
|
|
||||||
|
|
||||||
class FatalError(ProtocolError):
|
|
||||||
"""Exception raised when !fatal is received."""
|
|
||||||
|
|
||||||
|
|
||||||
class TrapError(ProtocolError):
|
|
||||||
"""
|
|
||||||
Exception raised when !trap is received.
|
|
||||||
|
|
||||||
:param int category: Optional integer representing category.
|
|
||||||
:param str message: Error message.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, category=None):
|
|
||||||
self.category = category
|
|
||||||
self.message = message
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.message.replace('\r\n', ','))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '{}({!r})'.format(self.__class__.__name__, str(self))
|
|
||||||
|
|
||||||
|
|
||||||
class MultiTrapError(ProtocolError):
|
|
||||||
"""
|
|
||||||
Exception raised when multiple !trap words have been received in one response.
|
|
||||||
|
|
||||||
:param traps: TrapError instances.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *traps):
|
|
||||||
self.traps = traps
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return ', '.join(str(trap) for trap in self.traps)
|
|
|
@ -1,26 +0,0 @@
|
||||||
from binascii import unhexlify, hexlify
|
|
||||||
from hashlib import md5
|
|
||||||
|
|
||||||
|
|
||||||
def encode_password(token, password):
|
|
||||||
#pylint: disable=redefined-outer-name
|
|
||||||
token = token.encode('ascii', 'strict')
|
|
||||||
token = unhexlify(token)
|
|
||||||
password = password.encode('ascii', 'strict')
|
|
||||||
hasher = md5()
|
|
||||||
hasher.update(b'\x00' + password + token)
|
|
||||||
password = hexlify(hasher.digest())
|
|
||||||
return '00' + password.decode('ascii', 'strict')
|
|
||||||
|
|
||||||
|
|
||||||
def token(api, username, password):
|
|
||||||
"""Login using pre routeros 6.43 authorization method."""
|
|
||||||
sentence = api('/login')
|
|
||||||
tok = tuple(sentence)[0]['ret']
|
|
||||||
encoded = encode_password(tok, password)
|
|
||||||
tuple(api('/login', **{'name': username, 'response': encoded}))
|
|
||||||
|
|
||||||
|
|
||||||
def plain(api, username, password):
|
|
||||||
"""Login using post routeros 6.43 authorization method."""
|
|
||||||
tuple(api('/login', **{'name': username, 'password': password}))
|
|
|
@ -1,209 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
|
|
||||||
from struct import pack, unpack
|
|
||||||
from logging import getLogger, NullHandler
|
|
||||||
|
|
||||||
from .exceptions import (
|
|
||||||
ProtocolError,
|
|
||||||
FatalError,
|
|
||||||
)
|
|
||||||
|
|
||||||
LOGGER = getLogger('librouteros')
|
|
||||||
LOGGER.addHandler(NullHandler())
|
|
||||||
|
|
||||||
|
|
||||||
def parse_word(word):
|
|
||||||
"""
|
|
||||||
Split given attribute word to key, value pair.
|
|
||||||
|
|
||||||
Values are casted to python equivalents.
|
|
||||||
|
|
||||||
:param word: API word.
|
|
||||||
:returns: Key, value pair.
|
|
||||||
"""
|
|
||||||
mapping = {'yes': True, 'true': True, 'no': False, 'false': False}
|
|
||||||
_, key, value = word.split('=', 2)
|
|
||||||
try:
|
|
||||||
value = int(value)
|
|
||||||
except ValueError:
|
|
||||||
value = mapping.get(value, value)
|
|
||||||
return (key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def cast_to_api(value):
|
|
||||||
"""Cast python equivalent to API."""
|
|
||||||
mapping = {True: 'yes', False: 'no'}
|
|
||||||
# this is necesary because 1 == True, 0 == False
|
|
||||||
if type(value) == int:
|
|
||||||
value = str(value)
|
|
||||||
else:
|
|
||||||
value = mapping.get(value, str(value))
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def compose_word(key, value):
|
|
||||||
"""
|
|
||||||
Create a attribute word from key, value pair.
|
|
||||||
Values are casted to api equivalents.
|
|
||||||
"""
|
|
||||||
return '={}={}'.format(key, cast_to_api(value))
|
|
||||||
|
|
||||||
|
|
||||||
class Encoder:
|
|
||||||
|
|
||||||
def encodeSentence(self, *words):
|
|
||||||
"""
|
|
||||||
Encode given sentence in API format.
|
|
||||||
|
|
||||||
:param words: Words to endoce.
|
|
||||||
:returns: Encoded sentence.
|
|
||||||
"""
|
|
||||||
encoded = map(self.encodeWord, words)
|
|
||||||
encoded = b''.join(encoded)
|
|
||||||
# append EOS (end of sentence) byte
|
|
||||||
encoded += b'\x00'
|
|
||||||
return encoded
|
|
||||||
|
|
||||||
def encodeWord(self, word):
|
|
||||||
"""
|
|
||||||
Encode word in API format.
|
|
||||||
|
|
||||||
:param word: Word to encode.
|
|
||||||
:returns: Encoded word.
|
|
||||||
"""
|
|
||||||
#pylint: disable=no-member
|
|
||||||
encoded_word = word.encode(encoding=self.encoding, errors='strict')
|
|
||||||
return Encoder.encodeLength(len(word)) + encoded_word
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encodeLength(length):
|
|
||||||
"""
|
|
||||||
Encode given length in mikrotik format.
|
|
||||||
|
|
||||||
:param length: Integer < 268435456.
|
|
||||||
:returns: Encoded length.
|
|
||||||
"""
|
|
||||||
if length < 128:
|
|
||||||
ored_length = length
|
|
||||||
offset = -1
|
|
||||||
elif length < 16384:
|
|
||||||
ored_length = length | 0x8000
|
|
||||||
offset = -2
|
|
||||||
elif length < 2097152:
|
|
||||||
ored_length = length | 0xC00000
|
|
||||||
offset = -3
|
|
||||||
elif length < 268435456:
|
|
||||||
ored_length = length | 0xE0000000
|
|
||||||
offset = -4
|
|
||||||
else:
|
|
||||||
raise ProtocolError('Unable to encode length of {}'.format(length))
|
|
||||||
|
|
||||||
return pack('!I', ored_length)[offset:]
|
|
||||||
|
|
||||||
|
|
||||||
class Decoder:
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def determineLength(length):
|
|
||||||
"""
|
|
||||||
Given first read byte, determine how many more bytes
|
|
||||||
needs to be known in order to get fully encoded length.
|
|
||||||
|
|
||||||
:param length: First read byte.
|
|
||||||
:return: How many bytes to read.
|
|
||||||
"""
|
|
||||||
integer = ord(length)
|
|
||||||
|
|
||||||
#pylint: disable=no-else-return
|
|
||||||
if integer < 128:
|
|
||||||
return 0
|
|
||||||
elif integer < 192:
|
|
||||||
return 1
|
|
||||||
elif integer < 224:
|
|
||||||
return 2
|
|
||||||
elif integer < 240:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
raise ProtocolError('Unknown controll byte {}'.format(length))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def decodeLength(length):
|
|
||||||
"""
|
|
||||||
Decode length based on given bytes.
|
|
||||||
|
|
||||||
:param length: Bytes string to decode.
|
|
||||||
:return: Decoded length.
|
|
||||||
"""
|
|
||||||
bytes_length = len(length)
|
|
||||||
|
|
||||||
if bytes_length < 2:
|
|
||||||
offset = b'\x00\x00\x00'
|
|
||||||
xor = 0
|
|
||||||
elif bytes_length < 3:
|
|
||||||
offset = b'\x00\x00'
|
|
||||||
xor = 0x8000
|
|
||||||
elif bytes_length < 4:
|
|
||||||
offset = b'\x00'
|
|
||||||
xor = 0xC00000
|
|
||||||
elif bytes_length < 5:
|
|
||||||
offset = b''
|
|
||||||
xor = 0xE0000000
|
|
||||||
else:
|
|
||||||
raise ProtocolError('Unable to decode length of {}'.format(length))
|
|
||||||
|
|
||||||
decoded = unpack('!I', (offset + length))[0]
|
|
||||||
decoded ^= xor
|
|
||||||
return decoded
|
|
||||||
|
|
||||||
|
|
||||||
class ApiProtocol(Encoder, Decoder):
|
|
||||||
|
|
||||||
def __init__(self, transport, encoding):
|
|
||||||
self.transport = transport
|
|
||||||
self.encoding = encoding
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def log(direction_string, *sentence):
|
|
||||||
for word in sentence:
|
|
||||||
LOGGER.debug('{0} {1!r}'.format(direction_string, word))
|
|
||||||
|
|
||||||
LOGGER.debug('{0} EOS'.format(direction_string))
|
|
||||||
|
|
||||||
def writeSentence(self, cmd, *words):
|
|
||||||
"""
|
|
||||||
Write encoded sentence.
|
|
||||||
|
|
||||||
:param cmd: Command word.
|
|
||||||
:param words: Aditional words.
|
|
||||||
"""
|
|
||||||
encoded = self.encodeSentence(cmd, *words)
|
|
||||||
self.log('<---', cmd, *words)
|
|
||||||
self.transport.write(encoded)
|
|
||||||
|
|
||||||
def readSentence(self):
|
|
||||||
"""
|
|
||||||
Read every word untill empty word (NULL byte) is received.
|
|
||||||
|
|
||||||
:return: Reply word, tuple with read words.
|
|
||||||
"""
|
|
||||||
sentence = tuple(word for word in iter(self.readWord, ''))
|
|
||||||
self.log('--->', *sentence)
|
|
||||||
reply_word, words = sentence[0], sentence[1:]
|
|
||||||
if reply_word == '!fatal':
|
|
||||||
self.transport.close()
|
|
||||||
raise FatalError(words[0])
|
|
||||||
return reply_word, words
|
|
||||||
|
|
||||||
def readWord(self):
|
|
||||||
byte = self.transport.read(1)
|
|
||||||
# Early return check for null byte
|
|
||||||
if byte == b'\x00':
|
|
||||||
return ''
|
|
||||||
to_read = self.determineLength(byte)
|
|
||||||
byte += self.transport.read(to_read)
|
|
||||||
length = self.decodeLength(byte)
|
|
||||||
word = self.transport.read(length)
|
|
||||||
return word.decode(encoding=self.encoding, errors='strict')
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.transport.close()
|
|
|
@ -1,64 +0,0 @@
|
||||||
# -*- coding: UTF-8 -*-
|
|
||||||
from itertools import chain
|
|
||||||
from .protocol import (
|
|
||||||
cast_to_api,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Key:
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
yield '?={}={}'.format(self, cast_to_api(other))
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
yield from self == other
|
|
||||||
yield '?#!'
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
yield '?<{}={}'.format(self, cast_to_api(other))
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
yield '?>{}={}'.format(self, cast_to_api(other))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class Query:
|
|
||||||
|
|
||||||
def __init__(self, path, keys, api):
|
|
||||||
self.path = path
|
|
||||||
self.keys = keys
|
|
||||||
self.api = api
|
|
||||||
self.query = tuple()
|
|
||||||
|
|
||||||
def where(self, *args):
|
|
||||||
self.query = tuple(chain.from_iterable(args))
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
keys = ','.join(str(key) for key in self.keys)
|
|
||||||
keys = '=.proplist={}'.format(keys)
|
|
||||||
cmd = str(self.path.join('print'))
|
|
||||||
return iter(self.api.rawCmd(cmd, keys, *self.query))
|
|
||||||
|
|
||||||
|
|
||||||
def And(left, right, *rest):
|
|
||||||
#pylint: disable=invalid-name
|
|
||||||
yield from left
|
|
||||||
yield from right
|
|
||||||
yield from chain.from_iterable(rest)
|
|
||||||
yield '?#&'
|
|
||||||
yield from ('?#&', ) * len(rest)
|
|
||||||
|
|
||||||
|
|
||||||
def Or(left, right, *rest):
|
|
||||||
#pylint: disable=invalid-name
|
|
||||||
yield from left
|
|
||||||
yield from right
|
|
||||||
yield from chain.from_iterable(rest)
|
|
||||||
yield '?#|'
|
|
||||||
yield from ('?#|', ) * len(rest)
|
|
|
@ -4,7 +4,9 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://github.com/tomaae/homeassistant-mikrotik_router",
|
"documentation": "https://github.com/tomaae/homeassistant-mikrotik_router",
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"requirements": [],
|
"requirements": [
|
||||||
|
"librouteros==3.0.0"
|
||||||
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@tomaae"
|
"@tomaae"
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,10 +2,7 @@
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
import importlib
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from .exceptions import ApiEntryNotFound
|
from .exceptions import ApiEntryNotFound
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -13,12 +10,7 @@ from .const import (
|
||||||
DEFAULT_ENCODING,
|
DEFAULT_ENCODING,
|
||||||
)
|
)
|
||||||
|
|
||||||
MODULE_PATH = os.path.join(os.path.dirname(__file__), "librouteros_custom", "__init__.py")
|
import librouteros
|
||||||
MODULE_NAME = "librouteros_custom"
|
|
||||||
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
|
|
||||||
librouteros_custom = importlib.util.module_from_spec(spec)
|
|
||||||
sys.modules[spec.name] = librouteros_custom
|
|
||||||
spec.loader.exec_module(librouteros_custom)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -84,13 +76,13 @@ class MikrotikAPI:
|
||||||
kwargs["ssl_wrapper"] = self._ssl_wrapper
|
kwargs["ssl_wrapper"] = self._ssl_wrapper
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
try:
|
try:
|
||||||
self._connection = librouteros_custom.connect(self._host, self._username, self._password, **kwargs)
|
self._connection = librouteros.connect(self._host, self._username, self._password, **kwargs)
|
||||||
except (
|
except (
|
||||||
librouteros_custom.exceptions.TrapError,
|
librouteros.exceptions.TrapError,
|
||||||
librouteros_custom.exceptions.MultiTrapError,
|
librouteros.exceptions.MultiTrapError,
|
||||||
librouteros_custom.exceptions.ConnectionClosed,
|
librouteros.exceptions.ConnectionClosed,
|
||||||
librouteros_custom.exceptions.ProtocolError,
|
librouteros.exceptions.ProtocolError,
|
||||||
librouteros_custom.exceptions.FatalError,
|
librouteros.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError
|
OSError
|
||||||
|
@ -147,16 +139,16 @@ class MikrotikAPI:
|
||||||
try:
|
try:
|
||||||
response = self._connection.path(path)
|
response = self._connection.path(path)
|
||||||
_LOGGER.debug("API response (%s): %s", path, response)
|
_LOGGER.debug("API response (%s): %s", path, response)
|
||||||
except librouteros_custom.exceptions.ConnectionClosed:
|
except librouteros.exceptions.ConnectionClosed:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return None
|
return None
|
||||||
except (
|
except (
|
||||||
librouteros_custom.exceptions.TrapError,
|
librouteros.exceptions.TrapError,
|
||||||
librouteros_custom.exceptions.MultiTrapError,
|
librouteros.exceptions.MultiTrapError,
|
||||||
librouteros_custom.exceptions.ProtocolError,
|
librouteros.exceptions.ProtocolError,
|
||||||
librouteros_custom.exceptions.FatalError,
|
librouteros.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -174,7 +166,7 @@ class MikrotikAPI:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tuple(response)
|
tuple(response)
|
||||||
except librouteros_custom.exceptions.ConnectionClosed as api_error:
|
except librouteros.exceptions.ConnectionClosed as api_error:
|
||||||
_LOGGER.error("Mikrotik %s error while path %s", self._host, api_error)
|
_LOGGER.error("Mikrotik %s error while path %s", self._host, api_error)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
@ -221,16 +213,16 @@ class MikrotikAPI:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
try:
|
try:
|
||||||
response.update(**params)
|
response.update(**params)
|
||||||
except librouteros_custom.exceptions.ConnectionClosed:
|
except librouteros.exceptions.ConnectionClosed:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return False
|
return False
|
||||||
except (
|
except (
|
||||||
librouteros_custom.exceptions.TrapError,
|
librouteros.exceptions.TrapError,
|
||||||
librouteros_custom.exceptions.MultiTrapError,
|
librouteros.exceptions.MultiTrapError,
|
||||||
librouteros_custom.exceptions.ProtocolError,
|
librouteros.exceptions.ProtocolError,
|
||||||
librouteros_custom.exceptions.FatalError,
|
librouteros.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -282,16 +274,16 @@ class MikrotikAPI:
|
||||||
try:
|
try:
|
||||||
run = response('run', **{'.id': tmp['.id']})
|
run = response('run', **{'.id': tmp['.id']})
|
||||||
tuple(run)
|
tuple(run)
|
||||||
except librouteros_custom.exceptions.ConnectionClosed:
|
except librouteros.exceptions.ConnectionClosed:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return False
|
return False
|
||||||
except (
|
except (
|
||||||
librouteros_custom.exceptions.TrapError,
|
librouteros.exceptions.TrapError,
|
||||||
librouteros_custom.exceptions.MultiTrapError,
|
librouteros.exceptions.MultiTrapError,
|
||||||
librouteros_custom.exceptions.ProtocolError,
|
librouteros.exceptions.ProtocolError,
|
||||||
librouteros_custom.exceptions.FatalError,
|
librouteros.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -336,16 +328,16 @@ class MikrotikAPI:
|
||||||
try:
|
try:
|
||||||
traffic = response('monitor-traffic', **args)
|
traffic = response('monitor-traffic', **args)
|
||||||
_LOGGER.debug("API response (%s): %s", "/interface/monitor-traffic", traffic)
|
_LOGGER.debug("API response (%s): %s", "/interface/monitor-traffic", traffic)
|
||||||
except librouteros_custom.exceptions.ConnectionClosed:
|
except librouteros.exceptions.ConnectionClosed:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return None
|
return None
|
||||||
except (
|
except (
|
||||||
librouteros_custom.exceptions.TrapError,
|
librouteros.exceptions.TrapError,
|
||||||
librouteros_custom.exceptions.MultiTrapError,
|
librouteros.exceptions.MultiTrapError,
|
||||||
librouteros_custom.exceptions.ProtocolError,
|
librouteros.exceptions.ProtocolError,
|
||||||
librouteros_custom.exceptions.FatalError,
|
librouteros.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue