I am trying to create an app to remotely control some lights. As for now it's going good, I can control the status of the relays that control the lights just by pushing the buttons I designed on the app regardless of the network the device on which the app is installed is connected.
What I want is:
Multiple devices will have this app installed and connected to the same place (I mean that will control the same lights). So when one changes the status of a light all the other instances of the app (the one making the changes included) should be updated with the new light statuses.
I thought to achieve this by putting a server listening inside the app so that when there's a change the real server, hosted on a Raspberry that controls a bunch of relays, can send a message to all the apps and update them. The apps will send their IP and the ID they are stored on the server everytime the app opens or there's an IP change.
The problem I'm facing is :
When working locally everything goes just fine, but when the device on which the app is installed connects to another network, it won't receive updates anymore. Of course I know this is due to the fact that, because of the Flutter Plugin I'm using, I only get the local IP of the device so through internet I won't reach it. I could retrieve both public and private IP addresses of a device, but then I don't know how to "ask" to the router which "owns" the public IP to let me access the device with the private IP without needing a portforwarding (portforwarding can't be done because I'm working with mobile devices).
My questions :
Is there a way to achieve what I want? Or a different method to dynamically update the apps whenever there's a change? Or a way to get both "external" and local IP and then call the apps using that?
Thanks in advance.
pubspec.yaml dependencies:
dependencies:
flutter:
sdk: flutter
get_ip: ^0.3.0
shared_preferences: ^0.4.3
AndroidManifest.xml permissions:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Flutter server and ip sending method:
void listenForUpdates()
{
HttpServer.bind(InternetAddress.anyIPv4, int.parse(port)).then((server)
{
server.listen((HttpRequest request)
{
MapEntry<String, String> parameter = request.uri.queryParameters.entries.single;
print(parameter);
setState(() {
lightsOn[int.parse(parameter.key) - 1] = parameter.value.contains('true');
noResponse = false;
});
request.response.close();
});
});
}
void initPlatformState(bool resync) async
{
String ipAddress;
ipAddress = await GetIp.ipAddress;
HttpClient client = new HttpClient();
SharedPreferences prefs = await SharedPreferences.getInstance();
setState((){ deviceId = resync ? "new" : (prefs.getString('deviceId') ?? "new"); });
client.postUrl(Uri.parse('http://NameOfTheServer:Port/?q=device&ip=' + ipAddress + ':' + port + '&id=' + deviceId))
.catchError((onError)
{
setState((){ noResponse = true; });
})
.then((HttpClientRequest request)
{
request.headers.set(HttpHeaders.userAgentHeader, 'LumenApp - Dart:Flutter');
return request.close();
})
.then((HttpClientResponse response)
{
response.transform(utf8.decoder).listen((contents) async
{
if(response.statusCode == 200)
{
setState((){
deviceId = contents.split('|')[1];
noResponse = false;
});
await prefs.setString('deviceId', deviceId);
}
else
setState((){ noResponse = true; });
});
});
}
Methods of the classes that handle the requests from the app (Raspberry, Java):
@Override
public void run()
{
System.out.println("Successfully started thread with ThreadId " + this.threadId + ".");
if(http.res.getCurrentResponse().size() > 0)
{
http.res.send();
}
else
{
http.res.setHeaders(http.req.getHttpVersion(), 200);
try
{
String qParam = http.req.getParameterValueByName("q");
String deviceId = http.req.getParameterValueByName("id");
Boolean devPerm = AppUpdater.getDevicesId().contains(deviceId);
if(!devPerm && !deviceId.equalsIgnoreCase("new"))
{
http.res.setBody("ID EXPIRED");
}
else
{
if(qParam.equalsIgnoreCase("device"))
{
String deviceIp = http.req.getParameterValueByName("ip");
String toDelete = http.req.getParameterValueByName("delete");
if(toDelete == null)
{
toDelete = "false";
}
Integer id = AppUpdater.updateDevicesIp(deviceId, deviceIp, Boolean.parseBoolean(toDelete));
http.res.setBody("DeviceId|" + id);
}
else if(Integer.parseInt(qParam) >= 0 && devPerm)
{
Integer lParam = Integer.parseInt(http.req.getParameterValueByName("l"));
gpioHandler.changeGpiosState(qParam, lParam);
}
else if(qParam.equals("-1") && devPerm)
{
HashMap<Integer, Boolean> gpiosState = gpioHandler.getGpiosState();
http.res.setBody(qParam);
for(HashMap.Entry<Integer, Boolean> gpio : gpiosState.entrySet())
{
http.res.addBody("|" + gpio.getKey().toString() + "-" + gpio.getValue().toString());
}
}
}
}
catch(RuntimeException e)
{
e.printStackTrace();
System.err.println("One or more required parameters were missing.");
http.res.setHeaders(http.req.getHttpVersion(), 400);
}
finally
{
http.res.send();
}
}
public class AppUpdater
{
static final GpioHandler gpioHandler = new GpioHandler();
static HashMap<String, String> devices = new HashMap<String, String>();
public static Integer updateDevicesIp(String deviceId, String deviceIp, boolean toDelete)
{
Integer id = 0;
if(toDelete)
{
devices.remove(deviceIp);
}
else if(deviceId.equalsIgnoreCase("new"))
{
id = devices.size() + 1;
devices.put(id.toString(), deviceIp);
}
else if(devices.containsKey(deviceId))
{
devices.replace(deviceId, deviceIp);
id = Integer.parseInt(deviceId);
}
return id;
}
public static void notifyApp(String lightIndex, String lightStatus) throws IOException
{
for(HashMap.Entry<String, String> device : devices.entrySet())
{
String urlParameters = "?" + lightIndex + "=" + lightStatus;
URL url = new URL("http://" + device.getValue() + urlParameters);
System.out.println(url);
url.openStream();
}
}
public static ArrayList<String> getDevicesId()
{
ArrayList<String> devicesId = new ArrayList<String>();
for(HashMap.Entry<String, String> device : devices.entrySet())
{
devicesId.add(device.getKey());
}
return devicesId;
}
public static HashMap<String, String> getDevices()
{
return devices;
}
}
public void gpioListener()
{
for(GpioPinDigitalOutput pin : gpios)
{
pin.addListener(new GpioPinListenerDigital() {
@Override
public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
{
String gpioName = event.getPin().getName();
System.out.println(" --> GPIO PIN STATE CHANGE: " + gpioName + " = " + event.getState());
try
{
String lightStatus = event.getState().toString().equals("HIGH") ? "true" : "false";
String pinIndex = pinIndexes.get(gpioName.substring(gpioName.length() - 1));
AppUpdater.notifyApp(pinIndex, lightStatus);
}
catch(IOException e)
{
e.printStackTrace();
}
}
});
}
}