первый коммит
This commit is contained in:
commit
59d159d683
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
src/env.py
|
||||||
|
|
23
README.md
Normal file
23
README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# esp-wssmqtt-board
|
||||||
|
|
||||||
|
Проект создан "на коленке" за пару вечеров.
|
||||||
|
|
||||||
|
Изначально разрабатывался для микроконтроллера ESP32, но можно использовать и ESP8266, и на rp2040(W), и возможно на pyboard.
|
||||||
|
|
||||||
|
Программа связывается с MQTT брокером через вебсокет (с TLS), подписывается на пачку топиков и слушает их.
|
||||||
|
Значения в топиках следует делать `retain`, чтобы при потере связи они снова отсылались прошивке.
|
||||||
|
|
||||||
|
# Инструментарий
|
||||||
|
|
||||||
|
Для запуска проекта на микроконтроллере нужен установленный micropython (логично).
|
||||||
|
|
||||||
|
Его установку тут описывать не буду.
|
||||||
|
|
||||||
|
# Перед запуском
|
||||||
|
|
||||||
|
Для настройки "переменных окружения" используется один единственный файл - `env.py`.
|
||||||
|
Перед заливкой прошивки нужно скопировать файл `example.env.py` в `src/env.py` и отредактировать его.
|
||||||
|
|
||||||
|
Дополнительно можно отредактировать файл с интерфейсами `src/intervaces.py`.
|
||||||
|
Там прописаны физические интерфейсы управления, можно добавлять/изменять/удалять существующие.
|
||||||
|
|
12
example.env.py
Normal file
12
example.env.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# настройки Wi-Fi
|
||||||
|
WIFI_SSID = "My ssid"
|
||||||
|
WIFI_PASSWORD = "supersecret-password-228"
|
||||||
|
|
||||||
|
# настройки вебсокета
|
||||||
|
WSS_HOST = "mqtt.mydamain.com"
|
||||||
|
WSS_PATH = "/mqtt"
|
||||||
|
|
||||||
|
# настройки авторизации
|
||||||
|
MQTT_USER = "mqtt_user"
|
||||||
|
MQTT_PASSWORD = "supersecret-mqtt"
|
||||||
|
|
122
src/interfaces.py
Normal file
122
src/interfaces.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import machine
|
||||||
|
import time
|
||||||
|
from machine import Pin, PWM
|
||||||
|
|
||||||
|
_U16_MAX = 0xFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def _int_to_servo_duty(val):
|
||||||
|
val = max(0, min(1000, val))
|
||||||
|
val += 1000 # все в микросекундах, получается диапазон [1000...2000]
|
||||||
|
# переводим в нужные числа
|
||||||
|
val = int(float(val) / 0.305180438)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def make_int_interpolate(left_min, left_max, right_min, right_max):
|
||||||
|
# Figure out how 'wide' each range is
|
||||||
|
leftSpan = left_max - left_min
|
||||||
|
rightSpan = right_max - right_min
|
||||||
|
|
||||||
|
# Compute the scale factor between left and right values
|
||||||
|
scaleFactor = float(rightSpan) / float(leftSpan)
|
||||||
|
|
||||||
|
# create interpolation function using pre-calculated scaleFactor
|
||||||
|
def interp_fn(value):
|
||||||
|
val = int(right_min + (value - left_min) * scaleFactor)
|
||||||
|
if val < right_min:
|
||||||
|
val = right_min
|
||||||
|
elif val > right_max:
|
||||||
|
val = right_max
|
||||||
|
return val
|
||||||
|
|
||||||
|
return interp_fn
|
||||||
|
|
||||||
|
|
||||||
|
class BaseInterface:
|
||||||
|
def __init__(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def get_topics(self):
|
||||||
|
return (self._name, )
|
||||||
|
|
||||||
|
def process_message(self, topic, value):
|
||||||
|
print(f"Process topic {topic} with '{value}' for {self}")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"<{self.__class__.__name__} {self._name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class PwmInterface(BaseInterface):
|
||||||
|
def __init__(self, name, pin, freq=2000, power_min=0, power_max=255, pwm_min=0, pwm_max=_U16_MAX):
|
||||||
|
super().__init__(name)
|
||||||
|
self._pin = pin
|
||||||
|
self._pwm = PWM(self._pin, freq=freq, duty_u16=0)
|
||||||
|
self._pwm_min = pwm_min
|
||||||
|
self._pwm_max = pwm_max
|
||||||
|
self._interpolator = make_int_interpolate(power_min, power_max, pwm_min, pwm_max)
|
||||||
|
self._power = pwm_min
|
||||||
|
self._powered = False
|
||||||
|
self._pwm.duty_u16(self._power)
|
||||||
|
self._topic_cmd = f"{self._name}/cmd"
|
||||||
|
self._topic_power = f"{self._name}/power"
|
||||||
|
|
||||||
|
def get_topics(self):
|
||||||
|
return self._topic_power, self._topic_cmd
|
||||||
|
|
||||||
|
def process_message(self, topic, value):
|
||||||
|
print(f"{self}: process topic {topic} with {value}")
|
||||||
|
if topic == self._topic_cmd:
|
||||||
|
if value == 'ON':
|
||||||
|
self._pwm.duty_u16(self._interpolator(self._power))
|
||||||
|
self._powered = True
|
||||||
|
else:
|
||||||
|
self._pwm.duty_u16(self._pwm_min)
|
||||||
|
self._powered = False
|
||||||
|
else:
|
||||||
|
self._power = int(value)
|
||||||
|
if self._powered:
|
||||||
|
self._pwm.duty_u16(self._interpolator(self._power))
|
||||||
|
|
||||||
|
|
||||||
|
class ServoInterface(PwmInterface):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs, freq=50, pwm_min=3276, pwm_max=6553)
|
||||||
|
# инициализация ESC
|
||||||
|
self._pwm.duty_u16(self._pwm_min)
|
||||||
|
time.sleep(0.2)
|
||||||
|
self._pwm.duty_u16(self._pwm_max)
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
self._pwm.duty_u16(self._power)
|
||||||
|
|
||||||
|
|
||||||
|
_interfaces = [
|
||||||
|
PwmInterface(f"/dorm/66/esp32-{machine.unique_id().hex()}/pwm0n0", Pin(13)),
|
||||||
|
PwmInterface(f"/dorm/66/esp32-{machine.unique_id().hex()}/pwm0n1", Pin(12)),
|
||||||
|
PwmInterface(f"/dorm/66/esp32-{machine.unique_id().hex()}/pwm0n2", Pin(14)),
|
||||||
|
PwmInterface(f"/dorm/66/esp32-{machine.unique_id().hex()}/pwm0n3", Pin(27)),
|
||||||
|
ServoInterface(f"/dorm/66/esp32-{machine.unique_id().hex()}/pwm1n0", Pin(32)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def list_topics():
|
||||||
|
out = []
|
||||||
|
for i in _interfaces:
|
||||||
|
for t in i.get_topics():
|
||||||
|
out.append(t)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
print(f"Interface names: {[i for i in list_topics()]}")
|
||||||
|
|
||||||
|
|
||||||
|
def process_message(topic, message):
|
||||||
|
topic = topic.decode('utf-8')
|
||||||
|
message = message.decode('utf-8')
|
||||||
|
print(f'Message received: topic={topic}, message={message}')
|
||||||
|
for i in _interfaces:
|
||||||
|
if topic in i.get_topics():
|
||||||
|
i.process_message(topic, message)
|
||||||
|
|
77
src/main.py
Normal file
77
src/main.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import errno
|
||||||
|
import network
|
||||||
|
import interfaces
|
||||||
|
import utime as time
|
||||||
|
from wssmqtt import *
|
||||||
|
from env import *
|
||||||
|
|
||||||
|
interfaces.init()
|
||||||
|
|
||||||
|
wifi = network.WLAN(network.STA_IF)
|
||||||
|
wifi.active(1)
|
||||||
|
wifi.connect(WIFI_SSID, WIFI_PASSWORD)
|
||||||
|
|
||||||
|
|
||||||
|
def do_connection():
|
||||||
|
# Create an MQTT client
|
||||||
|
keepalive = 5
|
||||||
|
client = WSSMQTTClient(host=WSS_HOST, path=WSS_PATH,
|
||||||
|
keepalive=25, user=MQTT_USER, password=MQTT_PASSWORD)
|
||||||
|
|
||||||
|
client.set_callback(interfaces.process_message)
|
||||||
|
|
||||||
|
# Publish messages to and receive messages from the MQTT broker forever
|
||||||
|
last_ping = time.ticks_ms()
|
||||||
|
while True:
|
||||||
|
if not client.is_connected():
|
||||||
|
print('Connecting...')
|
||||||
|
client.connect()
|
||||||
|
last_ping = time.ticks_ms()
|
||||||
|
|
||||||
|
for t in interfaces.list_topics():
|
||||||
|
print(f'Subscribing to topic {t}...')
|
||||||
|
client.subscribe(t)
|
||||||
|
|
||||||
|
print('Subscribed')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Periodically ping the broker consistently with the "keepalive"
|
||||||
|
# argument used when connecting. If this isn't done, the broker will
|
||||||
|
# disconnect when the client has been idle for 1.5x the keepalive
|
||||||
|
# time.
|
||||||
|
if (time.ticks_ms() - last_ping) >= keepalive * 1000:
|
||||||
|
# print('Ping!')
|
||||||
|
client.ping()
|
||||||
|
last_ping = time.ticks_ms()
|
||||||
|
|
||||||
|
# Every 5 minutes publish a message for fun.
|
||||||
|
# client.publish(topic, 'Coming from MicroPython via a websocket @%d' % (time.ticks_ms(),))
|
||||||
|
|
||||||
|
# Receive the next message if one is available, and handle the case that
|
||||||
|
# the broker has disconnected.
|
||||||
|
try:
|
||||||
|
client.check_msg()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
client.disconnect()
|
||||||
|
raise e
|
||||||
|
except BaseException as e:
|
||||||
|
if str(e) == '-1':
|
||||||
|
client.disconnect()
|
||||||
|
print('Connection closed')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
print("Try connection...")
|
||||||
|
do_connection()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
raise e
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOMEM:
|
||||||
|
machine.reset()
|
||||||
|
print(f"ERROR {e.__class__.__name__}")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
282
src/wssmqtt.py
Normal file
282
src/wssmqtt.py
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import ssl
|
||||||
|
import usocket as socket
|
||||||
|
import random
|
||||||
|
import binascii
|
||||||
|
import machine
|
||||||
|
from websocket import websocket
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketClient(websocket):
|
||||||
|
def __init__(self, sock):
|
||||||
|
super().__init__(sock)
|
||||||
|
self.__sock = sock
|
||||||
|
|
||||||
|
def setblocking(self, value):
|
||||||
|
self.__sock.setblocking(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_ssl_socket(hostname):
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
addr = socket.getaddrinfo(hostname, 443)[0][-1]
|
||||||
|
s.connect(addr)
|
||||||
|
return ssl.wrap_socket(s)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_websocket(hostname, path, protocol=None):
|
||||||
|
sock = WebSocketClient.create_ssl_socket(hostname)
|
||||||
|
|
||||||
|
query = f"GET {path} HTTP/1.1\r\n"
|
||||||
|
query += f"Host: {hostname}\r\n"
|
||||||
|
query += f"Connection: Upgrade\r\n"
|
||||||
|
query += f"Upgrade: websocket\r\n"
|
||||||
|
query += f"Sec-WebSocket-Key: {binascii.b2a_base64(bytes(random.getrandbits(8)
|
||||||
|
for _ in range(16)))[:-1].decode('utf-8')}\r\n"
|
||||||
|
query += f"Sec-WebSocket-Version: 13\r\n"
|
||||||
|
if protocol is not None:
|
||||||
|
query += f"Sec-WebSocket-Protocol: {protocol}\r\n"
|
||||||
|
query += f"\r\n"
|
||||||
|
|
||||||
|
sock.write(query.encode('utf-8'))
|
||||||
|
|
||||||
|
header = sock.readline()[:-2]
|
||||||
|
if not header:
|
||||||
|
sock.close()
|
||||||
|
print("ERROR: WebSocket not created (no response)")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not header.startswith(b'HTTP/1.1 101 '):
|
||||||
|
sock.close()
|
||||||
|
print(f"ERROR: WebSocket not created (server returns '{header.decode('utf-8')}')")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"INFO: WebSocket created! (server response: {header.decode('utf-8')})")
|
||||||
|
|
||||||
|
while header:
|
||||||
|
if header == b'\r\n':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(header)
|
||||||
|
header = sock.readline()
|
||||||
|
|
||||||
|
return WebSocketClient(sock)
|
||||||
|
|
||||||
|
|
||||||
|
class WSSMQTTException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WSSMQTTClient:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
host,
|
||||||
|
path='/',
|
||||||
|
client_id=f"client-{machine.unique_id().hex()}",
|
||||||
|
user=None,
|
||||||
|
password=None,
|
||||||
|
keepalive=0,
|
||||||
|
):
|
||||||
|
self.client_id = client_id
|
||||||
|
self.sock = None
|
||||||
|
self.host = host
|
||||||
|
self.path = path
|
||||||
|
self.pid = 0
|
||||||
|
self.cb = None
|
||||||
|
self.user = user
|
||||||
|
self.pswd = password
|
||||||
|
self.keepalive = keepalive
|
||||||
|
self.lw_topic = None
|
||||||
|
self.lw_msg = None
|
||||||
|
self.lw_qos = 0
|
||||||
|
self.lw_retain = False
|
||||||
|
|
||||||
|
def _send_str(self, s):
|
||||||
|
self.sock.write(struct.pack("!H", len(s)))
|
||||||
|
self.sock.write(s)
|
||||||
|
|
||||||
|
def _recv_len(self):
|
||||||
|
n = 0
|
||||||
|
sh = 0
|
||||||
|
while 1:
|
||||||
|
b = self.sock.read(1)[0]
|
||||||
|
n |= (b & 0x7F) << sh
|
||||||
|
if not b & 0x80:
|
||||||
|
return n
|
||||||
|
sh += 7
|
||||||
|
|
||||||
|
def set_callback(self, f):
|
||||||
|
self.cb = f
|
||||||
|
|
||||||
|
def set_last_will(self, topic, msg, retain=False, qos=0):
|
||||||
|
assert 0 <= qos <= 2
|
||||||
|
assert topic
|
||||||
|
self.lw_topic = topic
|
||||||
|
self.lw_msg = msg
|
||||||
|
self.lw_qos = qos
|
||||||
|
self.lw_retain = retain
|
||||||
|
|
||||||
|
def connect(self, clean_session=True):
|
||||||
|
if self.sock is not None:
|
||||||
|
raise WSSMQTTException('Already connected')
|
||||||
|
|
||||||
|
self.sock = WebSocketClient.create_websocket(self.host, self.path, protocol='mqtt')
|
||||||
|
if self.sock is None:
|
||||||
|
raise WSSMQTTException('Failed to create websocket')
|
||||||
|
|
||||||
|
try:
|
||||||
|
premsg = bytearray(b"\x10\0\0\0\0\0")
|
||||||
|
msg = bytearray(b"\x04MQTT\x04\x02\0\0")
|
||||||
|
|
||||||
|
sz = 10 + 2 + len(self.client_id)
|
||||||
|
msg[6] = clean_session << 1
|
||||||
|
if self.user:
|
||||||
|
sz += 2 + len(self.user) + 2 + len(self.pswd)
|
||||||
|
msg[6] |= 0xC0
|
||||||
|
if self.keepalive:
|
||||||
|
assert self.keepalive < 65536
|
||||||
|
msg[7] |= self.keepalive >> 8
|
||||||
|
msg[8] |= self.keepalive & 0x00FF
|
||||||
|
if self.lw_topic:
|
||||||
|
sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
|
||||||
|
msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
|
||||||
|
msg[6] |= self.lw_retain << 5
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
while sz > 0x7F:
|
||||||
|
premsg[i] = (sz & 0x7F) | 0x80
|
||||||
|
sz >>= 7
|
||||||
|
i += 1
|
||||||
|
premsg[i] = sz
|
||||||
|
|
||||||
|
self.sock.write(premsg, i + 2)
|
||||||
|
self.sock.write(msg)
|
||||||
|
# print(hex(len(msg)), hexlify(msg, ":"))
|
||||||
|
self._send_str(self.client_id)
|
||||||
|
if self.lw_topic:
|
||||||
|
self._send_str(self.lw_topic)
|
||||||
|
self._send_str(self.lw_msg)
|
||||||
|
if self.user:
|
||||||
|
self._send_str(self.user)
|
||||||
|
self._send_str(self.pswd)
|
||||||
|
resp = self.sock.read(4)
|
||||||
|
assert resp[0] == 0x20 and resp[1] == 0x02
|
||||||
|
if resp[3] != 0:
|
||||||
|
raise WSSMQTTException(resp[3])
|
||||||
|
return resp[2] & 1
|
||||||
|
except BaseException as e:
|
||||||
|
self.sock.close()
|
||||||
|
self.sock = None
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
if self.sock is None:
|
||||||
|
return
|
||||||
|
self.sock.write(b"\xe0\0")
|
||||||
|
self.sock.close()
|
||||||
|
self.sock = None
|
||||||
|
|
||||||
|
def is_connected(self):
|
||||||
|
return self.sock is not None
|
||||||
|
|
||||||
|
def ping(self):
|
||||||
|
self.sock.write(b"\xc0\0")
|
||||||
|
|
||||||
|
def publish(self, topic, msg, retain=False, qos=0):
|
||||||
|
pkt = bytearray(b"\x30\0\0\0")
|
||||||
|
pkt[0] |= qos << 1 | retain
|
||||||
|
sz = 2 + len(topic) + len(msg)
|
||||||
|
if qos > 0:
|
||||||
|
sz += 2
|
||||||
|
assert sz < 2097152
|
||||||
|
i = 1
|
||||||
|
while sz > 0x7F:
|
||||||
|
pkt[i] = (sz & 0x7F) | 0x80
|
||||||
|
sz >>= 7
|
||||||
|
i += 1
|
||||||
|
pkt[i] = sz
|
||||||
|
# print(hex(len(pkt)), hexlify(pkt, ":"))
|
||||||
|
self.sock.write(pkt, i + 1)
|
||||||
|
self._send_str(topic)
|
||||||
|
if qos > 0:
|
||||||
|
self.pid += 1
|
||||||
|
pid = self.pid
|
||||||
|
struct.pack_into("!H", pkt, 0, pid)
|
||||||
|
self.sock.write(pkt, 2)
|
||||||
|
self.sock.write(msg)
|
||||||
|
if qos == 1:
|
||||||
|
while 1:
|
||||||
|
op = self.wait_msg()
|
||||||
|
if op == 0x40:
|
||||||
|
sz = self.sock.read(1)
|
||||||
|
assert sz == b"\x02"
|
||||||
|
rcv_pid = self.sock.read(2)
|
||||||
|
rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
|
||||||
|
if pid == rcv_pid:
|
||||||
|
return
|
||||||
|
elif qos == 2:
|
||||||
|
assert 0
|
||||||
|
|
||||||
|
def subscribe(self, topic, qos=0):
|
||||||
|
assert self.cb is not None, "Subscribe callback is not set"
|
||||||
|
pkt = bytearray(b"\x82\0\0\0")
|
||||||
|
self.pid += 1
|
||||||
|
struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
|
||||||
|
# print(hex(len(pkt)), hexlify(pkt, ":"))
|
||||||
|
self.sock.write(pkt)
|
||||||
|
self._send_str(topic)
|
||||||
|
self.sock.write(qos.to_bytes(1, "little"))
|
||||||
|
while 1:
|
||||||
|
op = self.wait_msg()
|
||||||
|
if op == 0x90:
|
||||||
|
resp = self.sock.read(4)
|
||||||
|
# print(resp)
|
||||||
|
assert resp[1] == pkt[2] and resp[2] == pkt[3]
|
||||||
|
if resp[3] == 0x80:
|
||||||
|
raise WSSMQTTException(resp[3])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Wait for a single incoming MQTT message and process it.
|
||||||
|
# Subscribed messages are delivered to a callback previously
|
||||||
|
# set by .set_callback() method. Other (internal) MQTT
|
||||||
|
# messages processed internally.
|
||||||
|
def wait_msg(self):
|
||||||
|
res = self.sock.read(1)
|
||||||
|
if res is None:
|
||||||
|
return None
|
||||||
|
if res == b"":
|
||||||
|
raise OSError(-1)
|
||||||
|
self.sock.setblocking(True)
|
||||||
|
if res == b"\xd0": # PINGRESP
|
||||||
|
sz = self.sock.read(1)[0]
|
||||||
|
assert sz == 0
|
||||||
|
return None
|
||||||
|
op = res[0]
|
||||||
|
if op & 0xF0 != 0x30:
|
||||||
|
return op
|
||||||
|
sz = self._recv_len()
|
||||||
|
topic_len = self.sock.read(2)
|
||||||
|
topic_len = (topic_len[0] << 8) | topic_len[1]
|
||||||
|
topic = self.sock.read(topic_len)
|
||||||
|
sz -= topic_len + 2
|
||||||
|
if op & 6:
|
||||||
|
pid = self.sock.read(2)
|
||||||
|
pid = pid[0] << 8 | pid[1]
|
||||||
|
sz -= 2
|
||||||
|
msg = self.sock.read(sz)
|
||||||
|
self.cb(topic, msg)
|
||||||
|
if op & 6 == 2:
|
||||||
|
pkt = bytearray(b"\x40\x02\0\0")
|
||||||
|
struct.pack_into("!H", pkt, 2, pid)
|
||||||
|
self.sock.write(pkt)
|
||||||
|
elif op & 6 == 4:
|
||||||
|
assert 0
|
||||||
|
return op
|
||||||
|
|
||||||
|
# Checks whether a pending message from server is available.
|
||||||
|
# If not, returns immediately with None. Otherwise, does
|
||||||
|
# the same processing as wait_msg.
|
||||||
|
def check_msg(self):
|
||||||
|
self.sock.setblocking(False)
|
||||||
|
return self.wait_msg()
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user