wifi.removelistener() do not work properly

Posted on
  • Hi,
    I am so happy since I found esp and espruino, but I have a little problem. I have a small code with several listeners waiting wifi connection on boot and removing themselves afterward. It appears that the first to call wifi.removeListener remove not only this listener but also the following ones. Try for yourself :

    function start (){
    var wifi=require("Wifi");
       
    var f=function(){
      print("connected2 " );
      require("Wifi").removeListener("connecte­d",f);};
    
    wifi.on("connected",function(){print("co­nnected1");});
    wifi.on("connected",f);
    wifi.on("connected",function(){print("co­nnected3");});
    wifi.on("connected",function(){print("co­nnected4");});
    }
    
    E.on("init",start);
    save();
    

    Result is :

    connected1
    connected2
    

    I expected connected3 and connected4, but they never get displayed. But if I remove the wifi.removeListener(), they are displayed. I am removing listeners mostly in order to save memory.

    Am I am misunderstanding the situation ? is wifi.removeListener() working properly ?Please help.

    Sof

  • Sat 2017.07.22

    Welcome @user79576 I'm glad you have found enjoyment with the Espruino interpreter.

    Although I haven't tried out a fix to your particular issue, I believe the solution is to place the removeListener() function outside your creation of function f() as shown in the online reference:

    http://www.espruino.com/Reference#l_Obje­ct_removeListener

    As the var f is assigned the object result of the function creation, 'connected1' is output as expected, then function f() is created again, 'connected2' is output as expected then the object f() is removed. My guess is that processing is halting at this step as memory is corrupt, and the rest of the statements are ignored as you are seeing.

    Also, try:

    wifi.removeListener("connected",f);
    

    Other detail
    http://www.espruino.com/Reference#Wifi
    http://www.espruino.com/Reference#l_Wifi­_connected

  • Thank Robin for your answer,
    It may makes sense to not remove a listener that is currently executing, but I don't see how it is supposed to be done in practice. How can I call removeListener from outside the callback at the right moment (after wifi connected), without having a timer or another callback ? The example at http://microcosm.app/out/HSn2h is theorical, in practice you want to wait to receive the data before removing the listener (from inside the callback).
    I tried what you suggest with no change, usin "wifi.removeListener("connected",f);" instead of "require("Wifi").removeListener("connect­ed",f);".
    I tried also like this :

     setTimeout(function(){require("Wifi").re­moveListener("connected",f);},50);
    

    This give time for the first callback calls round to happen (connected1 to connected4), but then again the call remove f function callback and all following callbacks. The process is not only halted, but callbacks get removed. I can see each callbacks left with

    E.getSizeOf(global["\xFF"].modules.Wifi,­3);
    

    So next "connected" trigger only the first callback.

    My workaround now is to manage myself my wifi listeners and subscribe only one callback, or else when I can I subscribe listeners in the reverse order I plan to unsubscribe them. But I am left with the feeling that something is wrong and I can't put the finger on it.

    Sofyen

  • Hi,
    I don't understand why you are adding more than one listener function to the same event?

    Can't you do everything you need to do in the first connection function?

  • Hi Wilberforce,
    I can explain. I add more than one listener to the same event, because I am using modular code, where different part of the code are called independently : A module with a webserver that start listening when we connect, Another module that synchronize the internal clock with an sntp/http page when we connect, a module that save the current data remotely. I believe this is what the listeners are for. And I am removing them to spare memory, every callback holds its own code that take memory. If you have one giant callback doing everything, you cannot free the unnecessary code.

    Yes, I sure can use only one listener, that's the solution I mention, and I manage the callbacks myself, this way I can still have modules adding their listeners and removing them to free memory. But this is exactly what wifi.removeListener() was supposed to do.

  • Mon 2017.07.24

    @Polypod, have you solved the original removeListener() mystery or is there some doubt in the resolution you have settled with?

    While It is true that a listener can be connected to most objects as you so astutely described, after reading the online reference, I agree with @Wilberforce that it appears the WiFi connected object was meant to have a single listener.

    Polypod, if you are still wondering if you are at a sutiable solution, please post the entire code module as it now stands. This may provide
    others with clues that then could provide some insight. We all understand that you are taking a stab at a rather complex event model. I myself have had to post unfinished-unpolished intermediate segments and felt a bit uncomfortable waiting to arrive at a solution.

    "It is the answers, not the questions, that are embarrassing." Helen Suzman

    To those that are following along, is anyone able to post an example with multiple listeners or reference an online tutorial?

  • Hi Robin
    Thanks for the feedback. It triggered me into exploring workarounds. I am surprised that you say also that "wifi.on('connected',function)" is made for only one listner. I find that there is everything to work with multiple listner, but maybe not with a removal from within the listener.

    For example, here is the structure of the callback memory section of wifi module :

    E.getSizeOf(global["\xFF"].modules.Wifi,­ 2)
    =[
      {
        "name": "#onconnected",
        "size": 20,
        "more": [
          {
            "name": "0",
            "size": 20 },
          {
            "name": "1",
            "size": 20 }
         ]
       }
     ]
    

    In this example, we can see there is an array with "#onconnected" element with 2 callbacks in it. From there I was able to find a hacky solution to directly change the content of this hidden variable :

    function removeWifiListener(ev,fu) {
        var i=global["\xFF"].modules.Wifi["#on"+ev].­indexOf(fu);
        if(i!=-1) global["\xFF"].modules.Wifi["#on"+ev][i]­="";
      
        if(!global["\xFF"].modules.Wifi["#on"+ev­].some(function(e){return (e!=="");})) require("Wifi").removeAllListener(ev);
    }
    
    

    That's my best solution so far, nearest in behaviour to the original removeListener(e,f). I tried splicing this table, but of course if you do it from within the callback that is part of the table, it has the exact same problem than the original wifi.removeListener(e,f) I posted in the beginning.

    So my final guess is it was not designed to be changed from within the callback, because to do so, the array must be changed after iterating or a copy of the array is iterated and the original is modified. For those who like less hacky solutions, I explored these 2 options, but that are not as practical as my hacky solutions :

    // Solution 1 : Very near to what wifi module could be doing. The problem is that all calls must be centralized.
    
    
    var callbacks=new Array();
    function onWifiConnected(f) {
    	if(callbacks.length==0) require("Wifi").on("connected",wifiConne­cted);
    	callbacks.push(f);
    }
    
    function removeWifiListener(f) {
    	var i=callbacks.indexOf(f);
    	if(i!=-1) callbacks.splice(i, 1);
    	if(callbacks.length==0) require("Wifi").removeAllListeners("conn­ected");
    }
    
    function wifiConnected(d) {
    	callbacks.clone().forEach(function(f){f(­d);});
    }
    
    
    function start (){
    
    	var f1=function(){
    		print("connected1 " );
    		removeWifiListener(f1);
    	};
    	var f2=function(){
    		print("connected2 " );
    		removeWifiListener(f2);
    	};
    	var f3=function(){
    		print("connected3 " );
    //		removeWifiListener(f3);
    	};
    	var f4=function(){
    		print("connected4 " );
    		removeWifiListener(f4);
    	};
    
    	var wifi=require("Wifi");
    
    	onWifiConnected(f1);
    	onWifiConnected(f2);
    	onWifiConnected(f3);
    	onWifiConnected(f4);
    }
    
    E.on("init",start);
    save();
    
    // Solution 2 : not centralized, but using global vars and function redefinition. It does not remove the callback, but save the memory of the long code function t.
    
    function start (){
    	var i1=true;
    	var i2=true;
    	var i3=true;
    	var i4=true;
    
    	var t1=function(){
    		print("connected 1 " );
    		i1=false;
    	};
    	var t2=function(){
    		print("connected 2 " );
    		i2=false;
    	};
    
    	var t3=function(){
    		print("connected 3 " );
    //		i3=false;
    	};
    	var t4=function(){
    		print("connected 4 " );
    		i4=false;
    	};  
    
    	var wifi=require("Wifi");
    	wifi.on("connected",function(){
    		if(i1) {t1();if(!i1) t1="";}
    	});
    	wifi.on("connected",function(){
    		if(i2) {t2();if(!i2) t2="";}
    	});
    	wifi.on("connected",function(){
    		if(i3) {t3();if(!i3) t3="";}
    	});
    	wifi.on("connected",function(){
    		if(i4) {t4();if(!i4) t4="";}
    	});
    
    }
    E.on("init",start);
    save();
    
  • Mon 2017.07.24

    Thank you for posting your snippet. I see what you are doing with multiple connect objects, but I'm struggling as to why the need for several.

    I struggled with the different modules myself, but was able to use just one instance of the WiFi object.

    Although this is not a solution to what you are after, please read over this to see if it sparks some ideas.

    'STA or AP mode browser unable to connect to ESP8266-12'
    http://forum.espruino.com/conversations/­300549/

    The overall gist is to create a server/station object using the WiFi class module.

    I'm still searching for some code snippets to see if we are able to use a different class object rather than Wifi to accomplish what you are after.

  • Hi again Robin,
    I am making multiple independent modules(data logger, server, synchronize clock), that use wifi when connected. I also use only one instance of wifi module. But with only 1 listener, I have to go tell each module that now it is connected.
    I dont see how the conversation "'STA or AP mode browser unable to connect to ESP8266-12'" is connected to my problem.

    If you find anything that may help, it will be welcome

    Polypod

  • Thought that the gist ref #16 of just having one WiFi instance and using a server might replace what you had intended.

    Do any of these spark some ideas?

    http://www.espruino.com/Pico+Weather+Sta­tion

    http://www.espruino.com/wifi_humidity

    http://www.espruino.com/WiFi+Enabled+The­rmometer

    from:
    http://www.espruino.com/Tutorials

    Why not call each of your modules from inside the 'connected' function once connected?

    This would remove the need to 'pass' the concept of connected to each individual module, as each is only called once 'inside' the on-connected function. Ref Weather Station example above

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

wifi.removelistener() do not work properly

Posted by Avatar for Polypod @Polypod

Actions