【发布时间】:2020-04-17 16:12:59
【问题描述】:
我正在尝试创建一个应用程序来远程控制一些灯。 至于现在一切顺利,我可以通过按下我在应用程序上设计的按钮来控制控制灯的继电器的状态,而不管安装应用程序的设备连接的网络是什么。
我想要的是:
多个设备将安装此应用程序并连接到同一个地方(我的意思是这将控制相同的灯)。因此,当一个人更改灯的状态时,应用程序的所有其他实例(包括进行更改的实例)都应该使用新的灯状态进行更新。
我想通过在应用程序中放置一个服务器监听来实现这一点,这样当发生变化时,托管在控制一堆中继的 Raspberry 上的真实服务器可以向所有应用程序发送消息并更新它们。每次应用打开或发生 IP 更改时,应用都会发送其 IP 和存储在服务器上的 ID。
我面临的问题是:
在本地工作时一切正常,但是当安装了应用程序的设备连接到另一个网络时,它将不再接收更新。当然,我知道这是因为由于我使用的 Flutter 插件,我只能获得设备的本地 IP,因此通过互联网我无法访问它。我可以检索设备的公共 IP 地址和私有 IP 地址,但是我不知道如何“询问”“拥有”公共 IP 的路由器,让我无需端口转发即可使用私有 IP 访问设备(无法进行端口转发,因为我使用的是移动设备)。
我的问题:
有没有办法实现我想要的?还是在有变化时动态更新应用程序的不同方法?或者一种同时获取“外部”和本地 IP 然后使用它调用应用程序的方法?
提前致谢。
pubspec.yaml 依赖项:
dependencies:
flutter:
sdk: flutter
get_ip: ^0.3.0
shared_preferences: ^0.4.3
AndroidManifest.xml 权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Flutter服务器及ip发送方式:
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; });
});
});
}
处理来自应用程序(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();
}
}
});
}
}
【问题讨论】:
-
每个 Android 应用都包含一个服务器和一个客户端?
-
连接到另一个网络到底是什么意思?
-
@blackapps 是的,每个应用程序都包含一个服务器和一个客户端。连接到另一个网络是指设备更改了之前连接的网络,例如当设备从 wifi 切换到移动数据或另一个 wifi 网络时。
-
如果设备更改为移动网络,则其上的服务器将无用。
-
@blackapps 那么当应用发生变化时如何更新所有应用实例?
标签: java android networking flutter ip-address