Can not use wifi STA when using wifi AP mode
# help
r
Hello, I'm trying to scan the network on wifi STA mode, while wifi AP mode is up and running. But in the toit code, AP and STA modes cannot run at the same time. What is the reason for this? Trying to scan while network is established on AP:
Copy code
******************************************************************************
Decoding by `jag`, device has version <2.0.0-alpha.69>
******************************************************************************
EXCEPTION error. 
wifi already connected or established
  0: WifiServiceProvider.scan  /home/runner/work/toit/toit/system/extensions/esp32/wifi.toit:145:7
  1: WifiServiceProvider.handle /home/runner/work/toit/toit/system/extensions/esp32/wifi.toit:63:14
  2: ServiceManager_.<lambda>  <sdk>/system/services.toit:640:15
  3: RpcRequest_.process.<block> <sdk>/rpc/broker.toit:98:26
  4: RpcRequest_.process       <sdk>/rpc/broker.toit:95:3
  5: RpcRequestQueue_.ensure_processing_task_.<lambda>.<block>.<block> <sdk>/rpc/broker.toit:214:20
  6: RpcRequestQueue_.ensure_processing_task_.<lambda>.<block> <sdk>/rpc/broker.toit:209:9
  7: RpcRequestQueue_.ensure_processing_task_.<lambda> <sdk>/rpc/broker.toit:204:85
******************************************************************************
Line 143-153 in system/extensions/esp32/wifi.toit
Copy code
scan config/Map -> List:
    if state_.module:
      throw "wifi already connected or established"
    module := WifiModule.sta this "" ""
    try:
      channels := config.get wifi.CONFIG_SCAN_CHANNELS
      passive := config.get wifi.CONFIG_SCAN_PASSIVE
      period := config.get wifi.CONFIG_SCAN_PERIOD
      return module.scan channels passive period
    finally:
      module.disconnect
Does this AP+STA mode complicate things for toit, or is it because no one had the need for this? I just want to know if I should throw some hours into this
f
To be honest, I didn't even think it was possible.
Just searched a bit. AP+STA clearly should be possible.
From my few minutes of searching it might be possible to just change the
esp_wifi_set_mode
calls in
wifi_esp32.cc
.
r
I know tasmota uses both AP and STA in their captive portal, so it should be possible
f
I agree. I also found examples.
The big question is how much change is needed to Toit to support it.
r
Yes thats why I asked, I dont know how toit would work with it
f
My first attempt would be to just change all
esp_wifi_set_mode(WIFI_MODE_XXX)
to
esp_wifi_set_mode(WIFI_MODE_APSTA)
in wifi_esp32.cc.
r
Let me see if I can find tasmota's way of starting AP and STA seperetely
f
If we are lucky, you could then just use the wifi in both modes.
We might have some checks a bit earlier that make sure you don't try to use both at the same time. In that case we would need to remove/adjust them.
r
Yes theres checks in wifi.connect making sure only 1 connection is active etc
f
If that works, we should keep track of which mode is currently in use, and adjust the
set_mode
accordingly. (you don't want to enable AP, if it's not used).
can you point me to it? (which file)
r
I can easily test it, I have setup for using AP and STA
system/extentions/esp32/wifi.toit line 90
f
thanks.
r
Same with establish and wifi scan really
f
For testing I would just remove the check.
r
Yes
f
that said. It tries to create a new network resource. Not sure if that's allowed.
r
Yeah looks like some changes has to be made. Having control over multiple networks might not be easy
f
Looks all relatively straightforward but yes: not just 2 lines that need to be changed.
r
Looks like i can also look into this 🙂 seems possible
f
@kasperl has some TODOs in there. He might have some insights on how best to proceed.
Continued looking a bit.
And currently stuck at
esp_netif_new
in the
init
function (wifi_esp32.cc). It initializes it with
ESP_NETIF_DEFAULT_WIFI_AP
or
ESP_NETIF_DEFAULT_WIFI_STA
. But there isn't any
ESP_NETIF_DEFAULT_WIFI_APSTA
. So trying to see what should be done there...
There might be an easier way: potentially you could have multiple wifi interfaces. I have no idea how much we rely on it (so this might be extremely stupid), but you could add another entry to the resource pool (line 50 of wifi_esp32.cc) and allocate the two Wifi networks separately. Clearly that also requires to change the
esp_wifi_set_mode
, and potentially, calling
set_mode
clearl the existing configuration (in which case things get a bit more complicated again).
r
Hmm I will explore a bit now, and see if I can make it work
Copy code
/**
  * @brief  Start WiFi according to current configuration
  *         If mode is WIFI_MODE_STA, it create station control block and start station
  *         If mode is WIFI_MODE_AP, it create soft-AP control block and start soft-AP
  *         If mode is WIFI_MODE_APSTA, it create soft-AP and station control block and start soft-AP and station
  *
  * @return
  *    - ESP_OK: succeed
  *    - ESP_ERR_WIFI_NOT_INIT: WiFi is not initialized by esp_wifi_init
  *    - ESP_ERR_INVALID_ARG: invalid argument
  *    - ESP_ERR_NO_MEM: out of memory
  *    - ESP_ERR_WIFI_CONN: WiFi internal error, station or soft-AP control block wrong
  *    - ESP_FAIL: other WiFi internal errors
  */
esp_err_t esp_wifi_start(void);
third_party/esp-idf/components/esp_wifi/include/esp_wifi.h
line 294-308 It is definitely possible, but it does require some testing
f
From what I could see the problem is that we mix wifi and netif setup.
That is, we want two netif objects (one for sta and one for ap), but the wifi setup only exists once.
r
Yes netif did not have APSTA mode 😦
f
I think you should have two netif objects. (Which is actually much nicer anyway)
r
Also I noticed how some esp projects restart after having STA and AP open, to clear the esp after captive portal setup, and only use STA afterwards
f
Yes. You don't want to run either side if you don't need it.
(power...)
r
Yes but maybe restarting is easier than closing one of them 😄
f
probably 🙂
r
I will make a quick workaround with my current project, this looks a bit more difficult than what i wouldve imagined
f
Not sure what the easiest way forward is. (Mainly because there are many different strategies all with their advantages/disadvantages).
sounds good.
e
Is there any update on using softap_sta mode?
f
Not yet.
e
I've tried doing this with Toit, but it keeps failing after a few hours. Any idea why? I'll try changing the mode to see if that fixes it.
Copy code
run:
  while true:
    // Communicate with module network
    log.info "establishing wifi in AP mode ($CAPTIVE_PORTAL_SSID)"
    server-exception := catch:
      run-server
      network.close
    if server-exception:
      log.info "Server: $server-exception"
    sleep --ms=1000
    
    // Connect to LAN
    log.info "Attempting to connect to external WiFi"
    client-exception := catch:
      run-client
      network.close
    if client-exception:
      log.info "Client: $client-exception"
    sleep --ms=1000

run-server:
  try:
    wifi-exception := catch:
      network = wifi.establish
          --ssid=CAPTIVE-PORTAL-SSID
          --password=CAPTIVE-PORTAL-PASSWORD
      log.info "wifi established"      
    if wifi-exception:
      log.info "failed to establish AP"
      log.error wifi-exception
      
    exception := catch:
       with-timeout (Duration --m=1):
        run-http
    if exception:
      log.info "Breaking"
      log.info exception
      if exception == "Interrupt":
        throw exception
    log.info "wifi closing"
  finally:
    network.close

run-client:
  try:
    exception := catch:
      network = wifi.open --ssid=EXTERNAL-WIFI-SSID --password=EXTERNAL-WIFI-PASSWORD
      log.info "Connected to external WiFi"
      client := mqtt.Client --host=MQTT-HOST
      options := mqtt.SessionOptions
        --client-id=CLIENT-ID
        --username=MQTT-USERNAME
        --password=MQTT-PASSWORD
        --clean-session=true
      client.start --options=options
      payload := json.encode {
        "module": CLIENT-ID,
        "modules": modules.values
      }
      client.publish "mtsc" payload
      client.close
      log.info "MQTT message sent"
    if exception:
      log.error "Client: $exception"
  finally:
    network.close
f
Do you also have the beginning of when things go wrong? This looks like the wifi/Jaguar entered a bad state, but it would be interesting to see if there was a message when that happened
I'm assuming you are running with the Jaguar wifi disabled? If yes, then your program must have stopped/crashed, as the log indicates that Jaguar is trying to connect again to the wifi
e
Yes, I just saw that the problem is elsewhere. the server is running an http server and when the time is up for the AP, but a request is still ongoing an exception is thrown the socket
See run_http I was trying to catch the exception but it doesn't buble up from the lambda.
f
Do you have a stack trace?
I'm guessing the
listen
calls the lambda on a different task.
Might be impossible to catch with the current http package
Minor nits looking at your code: - Toit prefers
kebab-case
. You can use
jag toit tool kebabify code ...
to update your code. - toitdoc doesn't use leading
*
. You can use
jag toit doc
to see how your comments are currently interpreted. - we prefer to finish comments with a "." - toitdocs (
/**
with two stars) are only really used on methods, classes, ... but not inside functions
If you send me the uncatchable stacktrace I will have a look and try to see how to deal with it (potentially changing the http package)
e
Thank you. Yes, the code sucks. 🥹 I ll refactor it taking that into account.
f
I didn't say it sucks 🙂
The
jag decode
only works with the corresponding snapshot. Can you decode it?
f
Thanks. I will try to find some time next week. Feel free to ping me from time to time so I don't forget
e
Thanks. I ll also try to find a solution.
f
Your lambda is on the stacktrace. It should be able to stop the exception from bubbling up
I looked at your code. I think the problem is in your
manager-system/main.toit:230
. You call
listen
with a lambda, but sometimes throw (like
throw "Interrupt"
). The http package doesn't like that. Maybe (would need tests), it would be better to
cancel
the http-task when it doesn't behave the way you want.
Copy code
http-task := task:: ... listen ...
// Something happens that want to make you stop the server.
http-task.cancel.
e
Thank you. I tried before with a task, but I had the problem that I wasn't able to pass the reference to the lambda. I wanted to be able to stop the container using the HTTP API. Task.current.cancel didn't work. However, the idea above helped me with the exception issue. I've used another variable to stop the "run" task. There must be a better way to do this probably, but not sure how. Also I'm wondering if I switch to the softap_sta mode if doing sequential connections would be necessary...
Copy code
run:
  while true:
    // Communicate with module network
    log.info "establishing wifi in AP mode ($AP-SSID)"
    server-exception := catch:
      run-server
    if server-exception:
      log.info "Server: $server-exception"

    log.info (interrupt ? "Server interrupted, stopping..." : "Server timeout, sending info to external server...")

    if interrupt:
      break
    sleep --ms=1000
    
    // Connect to LAN
    log.info "Connecting to external network ($EXTERNAL-WIFI-SSID)"
    client-exception := catch:
      run-client
    if client-exception:
      log.info "Client: $client-exception"
    sleep --ms=1000

run-server:
  try:
    wifi-exception := catch:
      network = wifi.establish
          --ssid=AP-SSID
          --password=AP-PASSWORD
      log.info "AP established"      
    if wifi-exception:
      log.error "failed to establish AP"
      log.error wifi-exception
      
    exception := catch:
      log.info "Starting HTTP server"
      socket := network.tcp-listen 80
      server := http.Server --max-tasks=3
      http-task := task::
        server.listen socket:: | request writer |
          exception := catch:
            handle-http-request request writer
          if exception == "Interrupt":
            interrupt = true
          else if exception:
            log.error "Exception: HTTP - $exception"
          writer.close
      sleep (Duration --m=1)
      http-task.cancel
    if exception:
      log.error exception
  finally:
    log.info "HTTP server closing"
    network.close
That didn't work. I still get the exceptions. Also a new error came up see screenshots below.
f
Could you paste the stacktrace as text? Toit shouldn't crash and the stacktrace might help pinpoint the location in the C code where it failed.
Also, could you describe s bit more what exactly you are trying to achieve. It's much easier to review the code, understand the issues, and reproduce, once the purpose is clear.
e
The idea is for the esp32 to work as an AP that offers a HTTP API for a period of time (1m), and then to connect to another network periodically to send or receive data through the internet. The problem is that other clients make continuously requests, and at some point it seems that closing the network does not close requests that are currently being processed, and I'm not sure how to prevent that exception from bubbling up. maybe there is a better way of doing this...
f
It would be good if we figure out why you crashed, but I think a nicer solution is to modify the http-package so you can close the server. I created a PR: https://github.com/toitlang/pkg-http/pull/161 (Obviously, being able to have both running at the same time would be even better, but that requires much more work).
e
I'll add the log file later. Copying from tmux seems impossible when there is two panes.
f
If you are on Linux, try to select with "shift". That sometimes changes things for me.
holding shift and ctrl works. otherwise it selects both panes.