-
Notifications
You must be signed in to change notification settings - Fork 74
Add Server creation and management support #59
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
Conversation
Looks like you ran into some pylint issues. Let me know if you need some assistance in resolving them |
Thanks. I installed an in editor extension to lint as a type so shouldn't be seeing anymore of those errors moving forward. This PR is going to change a lot, as I originally had Server + AP in one, but we're going to be splitting them up. Since Server code is going to go in first, I'll need to do some git manipulation to get this PR to be independent of any AP code (#57) |
thank you for the effort, we'll get everything going, together! |
This is great, something I wanted to look into as well, thanks for doing this! A request handling like you suggested would be great, I think it should also be possible to serve .html files directly from the filesystem. Also, having a simple template system, but that's a separate library, I think. |
Yeah, definitely want to look into serving .html files from filesystem so you don't need to inline it in your python code, but I'm not sure yet if this would be a something built in to the lib or not. I also agree that a templating system would be separate. Next up I think is to figure out what the request handling looks like. I'm thinking
I'm curious if its possible to support both modes at the same time if you have the server request listening loop and callback handling on a separate thread from code.py? I'm not familiar with threading in python, let alone know what is possible in circuitpython/micropython, though. |
- can register callbacks on specific HTTP methods and paths - Can call update_poll in event loop to check for new incoming requests - update server example script
@arturo182 I agree with you that static asset handling is an important enough feature for getting a useful webserver running quickly. I've made it so you can configure a single directory as your 'static assets' folders by doing
and if a request comes through that doesn't match any of your own configured request handlers, but does match a GET request for one of the files in that directory, then that file will get automatically returned. It tries to determine the content-type based on the file extension. Right now I've only tested .html, .css, and .js, but images should work in theory, but might need some special handling. I've also made the method |
return True | ||
else: | ||
status = _the_interface.socket_status(self.socknum) | ||
# TODO: why is esp.<ConstantName> not defined? using magic numbers in mean time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to use the same constants as defined in adafruit_esp32spi
, but for some reason I wasn't able to get that to work. The constants weren't accessible off of the that module when running in CP (but my editor was showing them as available)
Any ideas? We obviously don't want the magic numbers here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which line are you referring to here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of the numbers in status not in (magic numbers)
All of those are constants corresponding to ones defined in esp32spi.py https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI/blob/master/adafruit_esp32spi/adafruit_esp32spi.py#L101-L111
My initial attempt to use them wasn't working, I'm sure there is just something silly i'm missing with being able to re-use those constants here.
else: | ||
status = _the_interface.socket_status(self.socknum) | ||
# TODO: why is esp.<ConstantName> not defined? using magic numbers in mean time | ||
result = status not in (1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for context, stole this logic from the arduino WiFiNINA
lib: https://github.com/arduino-libraries/WiFiNINA/blob/master/src/WiFiClient.cpp#L221-L242
examples/static/index.html
Outdated
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-minicolors/2.3.4/jquery.minicolors.min.js"></script> | ||
<script async src="led_color_picker_example.js"></script> | ||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-minicolors/2.3.4/jquery.minicolors.css" /> | ||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiarity with how licensing works with open source projects using other libraries. I double checked that jQuery, jQuery minicolors plugin, and bootstrap are all on MIT license, at least.
Let me know if this is not sufficient, we can re-write the the frontend to not need 3rd party libs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like an excellent start!! Thanks for this; I'm sure it will get used a lot.
Just a few things that jump out at me.
Note to other reviewers: I don't have any hardware to test this.
Thanks @mscosti ! This is all great work, definitely will be very useful and make it much easier to setup REST-ey or webservices or pages to interact with the hardware. My main concern right now is if the SPI32SPI library is the place to have a webserver live. It seems like this library should handle setting up the network connection and listening on a given port, but a separate library should be responsible for accepting and processing requests. Great work, I'm really looking forward to using this and helping out round out the feature set, but I think we should make sure it's being put in the right place. |
I don't disagree @siddacious , but I think it's a larger discussion. You're right that this adds a lot of functionality that is definitely not a part of purely sending and receiving commands to/from the esp32 over SPI. We have the But, this library has already gone past that line by introducing helper classes for making HTTP requests easier and parsing the response out into a response class (headers and body separated and all). So, I do agree with you, but if that's the line we want to draw then I think we would then also have to move over everything else that has an understanding of the HTTP protocol (incoming HTTP request handling/routing and responding. Outgoing request creation and response parsing). I can also see that server could be a little different because the feature set could grow pretty large, but it also doesn't have to. We just need to agree on where that line is drawn. Happy to continue to the discussion, I want this to end up in the right place too :) |
You're right @mscosti this certainly is a part of a larger discussion. This lib has definitely grown past purely talking to the ESP32 and has started including useful helpers for managing a wifi connection, making HTTP requests and packaging the responses into response objects as you say. While this is indeed beyond the obvious scope of "manage talking to the ESP32", what's been added is useful and for now at least makes sense to live here. I think the dividing line that makes sense to me is to have this library do an incoming/server side equivalent to the outgoing/client side helpers that already exist. The way I see it, the existing helpers allow the user to create an HTTP request , then it will send the request and package up the response for the user. In my mind, the correlate to that would be allowing the user to create an endpoint for clients to call, package up incoming requests into an object that something can be done with, and then handle returning the response to the user. In terms of how I would see that changing what you've already done, I would imagine that the |
I should have some time tomorrow to verify this on HW :) |
Thanks @arturo182 ! 😄 |
Oh damn, it slipped my mind! Will do it tomorrow for sure 😅 |
No rush @arturo182, my weeknights this week are a bit busy so I won't be able to do much until tomorrow and Friday, and I have to address some PR comments and possibly some re working depending on what feature set we want in this library anyway. Speaking of which: @siddacious I think I'm understanding you correctly but want to make sure and to clarify. I think what you're suggesting is that
That would then mean that the "endpoint registering and management" (on I guess, my only concern/question is, where do we think is the best place to put in all the extra server management goodies (both what we have right now + future features)? Would it be a new adafruit CP lib? The esp32SPI If we spin up a new lib, I feel like we would need a lot more design discussions to make sure we get it right. Another thing to think about is while it would be easy to just slot in what we have as middleware that goes on top of ESP32SPI (so it would likely have a dependency on ESP32SPI), if we put the effort in to make the new library I feel like it it shouldn't actually have the dependency at all and would need to be agnostic; the hardware api would need to conform to a strict interface. Otherwise there would be little gain in separating them out in the first place. Are there more circuit python wifi chips/coprocessors that can act as a server and would thus benefit from a generic server library? Sorry for the long train of thought post and if i'm over analyzing our options 😅 I'm just really torn between what is maybe more correct from an engineering perspective and with what provides the best user experience for the ESP32 airlift boards. We both already acknowledged it's a big discussion, so just adding more questions and things to think about to it. I'm curious what a path forward might look like. |
@mscosti it sounds like you understood my suggestion perfectly, based on your summary. In all candor I'm leaning on my experience with the Rack framework in Ruby: Rack defines a simple but robust interface for web apps or middleware to plug into a request handling stack on top of a range of different app servers. The whole middleware stack is way beyond what I have in mind which is more the clearly defined interface as you suggested. I just did a bit of looking and of course there is an equivalent standard in Python, WSGI: With regards to where the extra server stuff would go, I was imagining a separate lib. I agree that if the intent is for it to be transport/hardware agnostic it would make sense to have a strictly defined interface. Perhaps I'm being naive but I don't think we need to rack our brains too hard over the API. WSGI, Rack or something similar would be a perfectly good first pass and we could refine it later. The only other socket providing thing I know is somewhere near the horizon is the Ethernet FeatherWing though I imagine others are possible. It's certainly possible, even likely that Airlift/ESP32SPI would be the only hardware supporting the server library for a while. With that in mind I wouldn't be entirely opposed to keeping them together for the time being, though they would still want to be internally fairly distinct. Thanks for all the thought (and work!) you're putting into this. If you're feeling torn between between doing things "correctly" and what provides the best UI, in my opinion UI wins every time. I would suggest trying to avoid getting too caught up trying to find an optimal solution; better IMHO to iterate more quickly, fail early, and roll with the punches as they come. I'm happy to opine more but unless you have more questions for me, I'm content to leave it up to you to figure out the best approach. Other's opinions are always welcome and valued. |
@siddacious @brentru @sommersoft , I have completed the refactoring effort to have the main functionality that this lib provides to be a WSGI compatible server implementation, as discussed. https://www.python.org/dev/peps/pep-0333/ The examples folder now contains an example that includes a simple 'web application framework' to illustrate how to interface with the wsgi server. It shows how to write an application with basic custom request handlers and file serving. I have also addressed all PR comments with updated code or a follow up question/answer. |
- Already in ap_mode branch
examples/esp32spi_wsgiserver.py
Outdated
wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light) | ||
wifi.connect() | ||
|
||
class SimpleWSGIApplication: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to make a library to handle this class, called CircuitPython_WSGI
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, absolutely. With a new repo/library there are probably a lot more features we could add to make building web apps really simple. This is something I'd like to contribute to.
This is just a simple example of how to use the wsgi server that the esp32SPI library provides. It's possible we remove this part of the example from here when we have a separate and more full featured wsgi web app library. Until we have our own solution built though, I think its important to at least illustrate how to write a wsgi app; show that its really not that complicated, here is something to use as is for simple use case, or here is a starter base to add your own features on top of.
To me, that's the best part about using an interface like wsgi to define communication between application and server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mscosti Thanks for implementing the changes. I have some feedback regarding the example.
@brentru I have updated the PR with your feedback and is ready for re review whenever you have the chance |
@mscosti Thank you for making the requested changes - the example looks good to me. |
Thanks @brentru ! Does anyone else have any additional feedback to add? @siddacious @sommersoft @ladyada ? I'm also curious what the process is for getting things squashed and merged, and then released? |
@mscosti I'll do a test on a PyPortal later today with the current PR commit. |
@mscosti Could you put the esp32spi_wsgiserver.py and /static/ into one folder in Works perfectly on a PyPortal running CircuitPython 4.0.2. |
@brentru Thanks for testing! Moved static folder and example py script to a new folder located at |
@mscosti Looks OK to me for a merge into After this PR merges in, let's chat on discord about building out the WSGI library next (https://github.com/adafruit/Adafruit_CircuitPython_WSGI). |
Looks great! Merging! |
Updating https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI to 1.7.0 from 1.6.0: > Merge pull request adafruit/Adafruit_CircuitPython_ESP32SPI#61 from brentru/switch-requests > Merge pull request adafruit/Adafruit_CircuitPython_ESP32SPI#59 from mscosti/server Updating https://github.com/adafruit/Adafruit_CircuitPython_PyPortal to 3.1.2 from 3.1.0: > Merge pull request adafruit/Adafruit_CircuitPython_PyPortal#50 from brentru/switch-requests > Merge pull request adafruit/Adafruit_CircuitPython_PyPortal#49 from brentru/fix-tft-in-use > Merge pull request adafruit/Adafruit_CircuitPython_PyPortal#47 from jerryneedell/jerryn_fixcursor > Merge pull request adafruit/Adafruit_CircuitPython_PyPortal#42 from adafruit/ladyada-patch-1
This is still a work in progress, but opening it up for early feedback and suggestions.
Core Library additions
WSGIServer
classenvironment
dictionary with request values per wsgi requirements.start_response
method that must be invoked before theapplication
callable returnsNew example file
led_on
andled_off
handler to turn on /off an LEDajax/led_color
handler to set the LED colorTodo outside of this PR
Start discussing what a Circuit Python WSGI web application framework might look like.