-
Notifications
You must be signed in to change notification settings - Fork 138
Working with a wired LAN #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Since this library controls the WLAN component directly, it can't be used with a wired network (as it is). But it shouldn't be difficult to adapt it for a wired network.
(My fork: https://github.com/kevinkk525/micropython-mqtt Note that the constructor works slightly different, check the README. Apart from that, it's pretty much a drop-in replacement) |
Many thanks! if platform == "linux":
config["client_id"]="linux" |
Note that I have never worked with a wired LAN and I don't know if manual (re-)connects might be needed if the network adapter drops/loses the connection. In that case you'd need to program a workaround to reconnect the LAN and make a change to the constructor that lets you define the network interface so you can choose between WLAN and LAN. |
Debugged for Linux. Everything is working. def mqtt_callback(topic, msg, retained):
# if topic.startswith(b'dali/'):
# dali.receive_from_mqtt(topic, msg)
print(topic, msg, retained)
async def mqtt_conn_handler(client):
await client.subscribe('dali/#', 1)
mqtt = MQTT(host='192.168.1.107', user='alex', password='bh0020', subs_cb=mqtt_callback, conn_handler=mqtt_conn_handler,
debug=True)
async def main():
print('mqtt.connect')
await mqtt.connect()
print('mqtt.connected')
# await dali.run(mqtt.client)
await asyncio.sleep(1)
|
Is the webrepl reachable? Because it doesn't show the real ip Adress in what you posted. |
Webrepl runs as a server so maybe it behaves differently than outgoing connections. Does ntptime sync work or general network connections? Eg dns lookups |
ntptime works
this works too async def echo(reader, writer):
data = await reader.read(100)
print(data)
writer.write(data)
await writer.drain()
writer.close()
async def main():
asyncio.create_task(asyncio.start_server(echo, "192.168.1.34", 8034)) |
Looks line ntptime uses udp sockets. Can you test normal sockets? Just connect them to the broker like the examples of an echo client for python. It's not surorising to me that servers work, they care less about where a connection comes from, while an outgoing connection might try to use a different interface. Did you explicitly disable wlan interfaces? |
Ok, I'll try. boot.py import esp
esp.osdebug(None)
import webrepl
import machine
import network
lan = network.LAN(mdc=machine.Pin(23), mdio=machine.Pin(18), power=machine.Pin(16), phy_type=network.PHY_LAN8720,
phy_addr=1, clock_mode=network.ETH_CLOCK_GPIO0_IN)
lan.active(True)
lan.ifconfig(('192.168.1.34', '255.255.255.0', '192.168.1.1', '192.168.1.1'))
webrepl.start() |
Not turning it on doesn't necessarily mean it is off. Often it is needed to explicitly disable the wireless ap. So maybe try with st.active(False) and ap.active(False) |
import esp
esp.osdebug(None)
import webrepl
import machine
import network
wlan = network.WLAN(network.STA_IF)
ap = network.WLAN(network.AP_IF)
wlan.active(False)
ap.active(False)
lan = network.LAN(mdc=machine.Pin(23), mdio=machine.Pin(18), power=machine.Pin(16), phy_type=network.PHY_LAN8720,
phy_addr=1, clock_mode=network.ETH_CLOCK_GPIO0_IN)
lan.active(True)
lan.ifconfig(('192.168.1.34', '255.255.255.0', '192.168.1.1', '192.168.1.1'))
# webrepl.start() Implemented asyncio TCP echo server async def echo(reader, writer):
data = await reader.read(100)
print(data)
writer.write(data)
await writer.drain()
writer.close()
async def main():
asyncio.create_task(asyncio.start_server(echo, "192.168.1.34", 8034)) Only the first 8 sessions work. Then the port is closed. Sessions are normal. Here is one session for an example I'll try later without async |
As mentioned in my earlier replies, please test the echo CLIENT or just a simple, manual outgoing tcp connection to a server (either python echo server on your PC or to your mqtt broker directly) to see if outgoing connections work correctly. |
TCP client works without problems async def tcp_echo_client():
reader, writer = await asyncio.open_connection('192.168.1.107', 8888)
writer.write(b'test')
await writer.drain()
data = await reader.read(100)
print(data)
writer.close()
await writer.wait_closed()
async def main():
await asyncio.sleep(1)
i = 0
while True:
await asyncio.sleep(2)
i += 1
await tcp_echo_client()
print(i)
|
That's good. Can you please try one without asyncio too? |
Please add 119 and 118 to the list in line 38 (so that it looks like line 36). |
Hmm that means it couldn't connect.. Can you try to connect manually to the broker and your echo server? (without asyncio) |
Great, then try to connect to your mqtt broker the same way and send a message. |
Sorry I'm a bit out of ideas now. The previous test with the echo client and server worked so I would expect this to work now too (unless there is some error in your mqtt setup, credentials or similar. You don't have another mqtt client connected with the same ID?). But at least there's progress, it gets a connection even if not successful. |
Kevin, thank you very much for your great desire to help! Codeclass Dali:
def __init__(self, in_port, out_port, tcp_port=8998):
self._in_port = in_port
self._tcp_port = tcp_port
self._rmt = esp32.RMT(0, pin=Pin(out_port), clock_div=128)
self._command_queue_len = 0
self._busy = asyncio.Lock()
self._dali_send_buffer = [1, 0] + [0]*32
async def _serve(self, reader, writer):
print('command_queue_len:', self._command_queue_len)
if self._command_queue_len > 10:
writer.write(b'buffer_full')
await writer.drain()
writer.close()
await writer.wait_closed()
return
self._command_queue_len += 1
command = await reader.read(8)
async with self._busy:
try:
resp = await asyncio.wait_for(self.command_handler(command), timeout=1.0)
except asyncio.TimeoutError:
resp = b'timeout'
writer.write(resp)
await writer.drain()
writer.close()
await writer.wait_closed()
self._command_queue_len -= 1
async def start_tcp_server(self):
asyncio.create_task(asyncio.start_server(self._serve, "0.0.0.0", self._tcp_port))
async def send(self, b):
self._rmt.write_pulses(self._byte_to_rmt(b), start=1)
async def command_handler(self, cmd):
print(cmd)
ret = b'unknown'
if len(cmd) != 3:
return ret
command = cmd[2]
address = cmd[1]
value = cmd[0]
print('command', command, 'address', address, 'value', value)
if command == 1 and 0 <= address < 64:
ret = await self._cmd_set_level(address, value)
elif command == 2 and 0 <= address < 15:
ret = await self._cmd_set_level_grp(address, value)
return ret
async def _cmd_set_level(self, address, value):
print('cmd_set_level address:', address)
return b'OK'
async def _cmd_set_level_grp(self, address, value):
print('cmd_set_level_grp address:', address)
if value == 255:
value = 254
# send
address = address | 0b10000000
await self.send(bytearray([value, address]))
await asyncio.sleep_ms(20)
return b'OK'
async def _cmd_find(self):
pass
def _byte_to_rmt(self, b):
b = bytes(b)
for i in range(16):
if b[i // 8] & 1 << i % 8 != 0:
self._dali_send_buffer[i * 2 + 2] = 1
self._dali_send_buffer[i * 2 + 3] = 0
else:
self._dali_send_buffer[i * 2 + 2] = 0
self._dali_send_buffer[i * 2 + 3] = 1
rmt = []
compared = False
for i in range(34):
try:
if compared:
compared = False
continue
if self._dali_send_buffer[i] == self._dali_send_buffer[i + 1]:
rmt.append(520)
compared = True
else:
rmt.append(260)
except IndexError:
if i % 2:
rmt.append(1300) # 260*5
else:
rmt.append(260)
rmt.append(1040) # 260 * 4
pass
return rmt |
I'm too looking for an async mqtt client that works with a wired connection and I've found various async mqtt clients that implement more or less the same functionality. Imho it would make sense to move the resilient interface handling into a own class. Have you guys thought about that and was it a conscious decision against it or have the mqtt drivers "grown" to the state they are now? |
Coping with the inherent unreliability of WiFi was a core concept with the aim being to provide reliable MQTT on ESP8266 and ESP32. It was subsequently successfully tested on Pyboard D. A version of mqtt_as for a wired LAN would be a worthwhile project, which could result in a substantial simplification. A first step would be to consider whether to adapt mqtt_as for wired connectivity or to port an existing asynchronous MQTT library to MicroPython. I am busy with another project so this isn't something I plan to tackle at present. |
Thanks for your reply. I think I might have left some things unclear.
For the change to work, the interface must be abstracted so mqtt_as is agnostic the interface it is using. I am currently abstracting my interface handling in a class as an agnostic interface. |
I never worked with wired lans on microcontrollers but even though the are more stable than wifi, there are still things to consider like: cable reconnects, interferences from nearby devices that might scramble communication/network link temporarily and maybe other reasons why a message could fail or even the interface connection would fail temporarily. How is that case handled by micropython? Maybe a manual/automatic reconnect of the interface will be needed. (To support the unix port on my fork I made some changes that ignore all interface code in mqtt_as and the changes are rather small, so it shouldn't be too hard to separate the interface specific code from the mqtt client) |
The project had two specific objectives:
Wired interfaces were never considered.
My own view is that it depends :) I would be reluctant to significantly increase code size, as this would risk breaking ESP8266 applications. I would also have a problem testing any changes as I would need to buy wired microcontroller hardware. If the changes could be implemented in a subclass, leaving existing code unchanged, that would be ideal but I doubt this is practicable. Another option would be to implement (and document) the changes in a fork. That would enable you to merge any bugfixes we might implement. I would be happy to update our docs to direct users who need your functionality to the fork. @kevinkk525 I would be interested to hear your views. |
Imho device specific implementations could be split out in different files. E.g. a Wifi handler for ESP, pyboard, etc.
Since I additionally run a small webserver, I need to recreate it, too. Otherwise it won't serve the pages properly. # Wifi specific implementation
class IfWifi(InterfaceBase):
def __init__(self):
super(IfWifi, self).__init__(network.WLAN(network.STA_IF), auto_reconnect=True)
async def do_connect(self):
# interface specific connect logic
self._if.connect(CONFIG_WIFI['ssid'], CONFIG_WIFI['pass'])
while self._if.status() == network.STAT_CONNECTING:
await sleep_ms(200)
...
async def do_disconnect(self):
# disconnect logic
pass
WIFI = IfWifi()
async def disconnected():
print('disconnected)
async def connected():
print('connected)
# subscribe callbacks through base class
WIFI.on_connect(connected)
WIFI.on_disconnect(disconnected)
# interface that can be used by the code
await WIFI.connect()
await WIFI.disconnect()
await WIFI.check()
# vars that make additional logic easier
WIFI.is_connected
WIFI.has_connected
WIFI.first_connect
WIFI.is_connecting |
I think it may be time for the library to "evolve". There are more and more microcontrollers with micropython and they have different ways of connecting to a network (wifi, ethernet, maybe even some sort of bridges/proxy for boards without wifi, unix/windows ports).
This library is the most mature mqtt client based on uasyncio and considering the small amount of developers, I think it would be best to concentrate the efforts into maintaining a single library that fits all use-cases.
A resilient behaviour will be needed for every kind of connection because sometimes things go wrong at completely unexpected points. Admittedly, the wifi link is the most unreliable but afaik the esp8266 is the board with the lowest amount of RAM and boards without wifi often have lots of RAM, so even keeping a bit of "unneccessary" overhead in terms of unneeded resiliency will not make a difference on those boards.
Keeping the code size down on esp8266 is of course important and I think this could work. (A thought beyond the scope of this issue might be to think about offering a prebuilt firmware with certain popular and well maintained libraries like mqtt_as so that users don't have to either build their own firmware or cross-compile it or worse, upload the .py files and waste half the RAM on the esp8266. A small change in codesize would be irrelevant if the library was frozen but new users won't start freezing modules and therefore run into trouble while comiling their projects on the board.) As for testing: I understand that you will have to rely on other users to test the implementation and I think that shouldn't be a problem. If the mqtt code is separated from the hardware, you can just add hardware modules that have been tested and list the tested modules in the README. Others users can then easily add more modules or create PRs to have them added.
The easiest way might be to provide a base network class showing the structure:
Then depending on the hardware you can just subclass this and make it connect to a wifi or other interface. The existing wifi interface code in mqtt_as can be easily moved into such a class. The mqtt_as class would then just take such an interface class in the constructor (or create an interface class if the connection type is given, e.g. "wlan"). The rest of the code would then just call the corresponding coroutine of the interface class. This way the code size shouldn't change significantly and we can implement a compatibility mode for existing installations by creating a wifi interface automatically if the config dict contains the wlan configurations. (when doing all these changes we could also think about making the socket providable in the constructor in case someone wants to use some kind of proxy like a wlan link over SPI or similar) |
@kevinkk525 Another benefit would be that the connection logic for wifi is only implemented once and can be shared across projects. I've created a small ghist of my current implementation and I'd really like to hear your ideas and feedback about it. |
Sadly micropython doesn't offer a way to subscribe to interface changes so all we can do is poll the interface status and then call a callback. (but I think I understand what you mean) At the moment mqtt_as does have a callback for wifi state changes and for compatibility reasons this should be retained. I didn't think about that part in my last message and I agree that this should be handled by the interface class too. But it's easy to implement, you just need to call the callback in the coroutine interface.isconnected() whenever the state changes.
It's entirely up to you to create an interface class that can be used across all your projects. This new approach offers the user a lot of options and freedome.
This approach is rather complex. It might be good for your project but generall it's like taking a sledgehammer to crack a nut. The only thing to think about is that mqtt_as currently requests and interface reconnect every time the connection breaks. This might not be a desired behaviour on interfaces other than wifi or if you have multiple projects using the same interface. |
What I meant is that currently there are device specific workarounds for wifi connection (e.g. pyboard, esp32, esp8266).
That's what I meant.
That's why I differentiated between the connect request by the code (
Hm - imho it's not too bad. If you look at the usage example it's rather simple. Do you think we can work together on a implementation so we/I can publish a stable package? |
Yes that would be my goal too. Have a good reference implementation for the wlan interface for those boards within the repository (the code for that already exists within mqtt_as anyway) and then anyone who needs different interfaces can just create those themselves starting from the existing "blueprint".
Yes, just one class is needed that polls the interface and since we're programming with uasyncio, it could theoretically be another task that continuously polls the interface and serves all event subscribers. That's of course not how mqtt_as currently works but we can think about implementations.
I see, yes I do things like that too in other applications but typically you would have a public interface "connect" and an internal, private interface "_connect".
Simplicity in usage is not what I meant. Within the mqtt_as project we need a simple approach in terms of code size and execution requirements. The goal is to separate the core mqtt client from the hardware interface without adding complexity and further features. The hardware interface can always be extended in separate repositories.
I think we can do that. Let's each write something up about the goals and how to approach and solve the problems. Then we can discuss the differences better. Maybe open a new issue for this. |
Guess I was faster than I thought, took an hour today, see #57 |
Why did you bind the socket to the Interface? It won't work if I have two applications that are using the socket. I still think it makes sense to add the possibility to subscribe to the respective connect and disconnect. Additionally I thought this would be more of a discussion so we can iterate to a good solution. |
Only the socket module is bound to the interface so a custom socket implementation could be used. The socket object itself is still bound to the mqtt object.
I don't understand what you mean by that. Could you please explain that more detailed?
Currently the callback is always called after a disconnect or a connect. However, it might indeed be better to move the callback code into the (dis)connect methods rather than the isconnected method because this one would have to be actively called for the callbacks to work.
Not sure what you mean by that.
I think that's how it is done. A private method is by definition private and not a public method, therefore not part of the public interface.
It still is. I just provided an untested draft and as written in the PR I am looking for feedback. Please present your concerns, different opinions, solutions, etc. |
@kevinkk525 : Sorry if my comment sounded rude or annoyed- I've had a rough couple of days.
I missed that it's only the socket module. I'll leave my comments in your PR. |
It's all good. Thanks for your comments. Hope your days get better. |
just wanted to know if any progress has been made with wired ethernet, and I left a message with the person who was working on it last |
No. Unfortunately @kevinkk525 is unable to work on this, and I think something more radical is required. This library was originally developed to fix the problems with the official library on ESP8266. Coverage was extended to other WiFi capable platforms as they emerged. By contrast MQTT was developed to provide reliable communications over arbitrary unreliable media including radio links where there is no underlying network architecture. Rather than extending |
Almost all of my projects on microcontrollers use your mqtt_as program for comms over wifi to my rpi's or my mac computer, but I would also like a wired ethernet asyncio capability as well. I'm not advanced in my python to really follow your program without a deal of head scratching so I cant offer to assist in the programming much as I would like to, but I do volunteer to do end user testing for any project that may come to fruition. I do hope you get the necessary assistance for your proposed MQTT library as it would help to future proof it and perhaps make it part of the micropython distribution. If you got a bit carried away could it lead to an async mqtt library for python 3 as well ..... sorry thats a bit cheeky. |
Closing as I can't currently see a way forward on this. |
Will the library work with a wired network, such as a ESP32 + ethernet LAN8720A?
The text was updated successfully, but these errors were encountered: