需要全部代码请点赞关注收藏后评论区留言私信~~~
传统的定位方式不适用于室内的垂直定位,原因如下: (1)卫星定位要求没有障碍物遮挡,它在户外比较精准,在室内信号就变差。 (2)基站定位依赖于运营商的通讯服务,如果身处基站信号尚未覆盖的偏僻空间,就无法使用基站定位。 室内WiFi定位纳入了IEEE的802.11标准,名叫WLAN RTT (IEEE 802.11mc)。
RTT是Round-Trip-Time的缩写,即往返时间,可以用于计算网络两端的距离
室内WiFi定义的实现步骤有以下三步
(1)检查是否连接无线网络 通过无线网络管理器WifiManager获取WiFi信息。
(2)扫描周围的无线网络 用到无线网络管理器的startScan和getScanResults两个方法。
(3)计算WiFi路由器的往返时延 通过WifiRttManager对目标路由器测距。
上网方式主要有两种 即数据连接和WiFi,不过连接管理器ConnectivityManager只能笼统地判断能否上网,并不能获知WiFi连接的详细信息,在当前网络类型是WiFi时,要想得知WiFi上网的具体信息,还需另外通过无线网络管理器WifiManager获取 它的部分方法如下
isWifiEnabled 判断WLAN功能是否开启
setWifiEnabled 开启或关闭WLAN功能
getWifiState 获取当前的WiFi连接状态
实战效果如下 会输出所连接Wifi的相关信息

代码如下
package com.example.location;
import com.example.location.util.IPv4Util;
import com.example.location.util.NetUtil;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Looper;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.widget.TextView;
@SuppressLint("DefaultLocale")
public class WifiInfoActivity extends AppCompatActivity {
private static final String TAG = "WifiInfoActivity";
private TextView tv_info; // 声明一个文本视图对象
private Handler mHandler = new Handler(Looper.myLooper()); // 声明一个处理器对象
private String[] mWifiStateArray = {"正在断开", "已断开", "正在连接", "已连接", "未知"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wifi_info);
tv_info = findViewById(R.id.tv_info);
mHandler.postDelayed(mRefresh, 50); // 延迟50毫秒后启动网络刷新任务
}
// 定义一个网络刷新任务
private Runnable mRefresh = new Runnable() {
@Override
public void run() {
getAvailableNet(); // 获取可用的网络信息
// 延迟1秒后再次启动网络刷新任务
mHandler.postDelayed(this, 1000);
}
};
// 获取可用的网络信息
private void getAvailableNet() {
String desc = "";
// 从系统服务中获取电话管理器
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
// 从系统服务中获取连接管理器
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
// 通过连接管理器获得可用的网络信息
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.getState() == NetworkInfo.State.CONNECTED) { // 有网络连接
if (info.getType() == ConnectivityManager.TYPE_WIFI) { // WiFi网络(无线热点)
// 从系统服务中获取无线网络管理器
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
int state = wm.getWifiState(); // 获得无线网络的状态
WifiInfo wifiInfo = wm.getConnectionInfo(); // 获得无线网络信息
String SSID = wifiInfo.getSSID(); // 获得无线网络的名称
if (TextUtils.isEmpty(SSID) || SSID.contains("unknown")) {
desc = "\n当前联网的网络类型是WiFi,但未成功连接已知的WiFi信号";
} else {
desc = String.format("当前联网的网络类型是WiFi,状态是%s。\nWiFi名称是:%s\n路由器MAC是:%s\nWiFi信号强度是:%d\n连接速率是:%s\n手机的IP地址是:%s\n手机的MAC地址是:%s\n网络编号是:%s\n",
mWifiStateArray[state], SSID, wifiInfo.getBSSID(),
wifiInfo.getRssi(), wifiInfo.getLinkSpeed(),
IPv4Util.intToIp(wifiInfo.getIpAddress()),
wifiInfo.getMacAddress(), wifiInfo.getNetworkId());
}
} else if (info.getType() == ConnectivityManager.TYPE_MOBILE) { // 移动网络(数据连接)
int net_type = info.getSubtype();
desc = String.format("\n当前联网的网络类型是%s %s",
NetUtil.getNetworkTypeName(tm, net_type),
NetUtil.getClassName(tm, net_type));
} else {
desc = String.format("\n当前联网的网络类型是%d", info.getType());
}
} else { // 无网络连接
desc = "\n当前无上网连接";
}
tv_info.setText(desc);
}
}
扫描周边Wifi主要用到WiFi滚力气的startScan方法和getScanResults方法,前者表示开始扫描周围的网络,后者表示获取扫描的结果列表,它们两个方法不能紧跟着,因为扫描动作是异步进行的,必须等到收到扫描结束的广播,然后在广播接收器中才能获取扫描结果
点击开始扫描后结果如下 会输出附近的WiFi信息

代码如下
package com.example.location;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.TextView;
import com.example.location.adapter.ScanListAdapter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiresApi(api = Build.VERSION_CODES.M)
public class WifiScanActivity extends AppCompatActivity {
private final static String TAG = "WifiScanActivity";
private TextView tv_result; // 声明一个文本视图对象
private ListView lv_scan; // 声明一个列表视图对象
private WifiManager mWifiManager; // 声明一个WiFi管理器对象
private WifiScanReceiver mWifiScanReceiver = new WifiScanReceiver(); // 声明一个WiFi扫描接收器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wifi_scan);
tv_result = findViewById(R.id.tv_result);
lv_scan = findViewById(R.id.lv_scan);
findViewById(R.id.btn_scan).setOnClickListener(v -> mWifiManager.startScan());
// 从系统服务中获取WiFi管理器
mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
registerReceiver(mWifiScanReceiver, filter); // 注册WiFi扫描的广播接收器
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mWifiScanReceiver); // 注销WiFi扫描的广播接收器
}
// 定义一个扫描周边WiFi的广播接收器
private class WifiScanReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取WiFi扫描的结果列表
List<ScanResult> scanList = mWifiManager.getScanResults();
if (scanList != null) {
// 查找符合80211标准的WiFi路由器集合
Map<String, ScanResult> m80211mcMap = find80211mcResults(scanList);
runOnUiThread(() -> showScanResult(scanList, m80211mcMap));
}
}
}
// 查找符合80211标准的WiFi路由器集合
private Map<String, ScanResult> find80211mcResults(List<ScanResult> originList) {
Map<String, ScanResult> resultMap = new HashMap<>();
for (ScanResult scanResult : originList) { // 遍历扫描发现的WiFi列表
if (scanResult.is80211mcResponder()) { // 符合80211标准
resultMap.put(scanResult.BSSID, scanResult); // BSSID表示MAC地址
}
}
return resultMap;
}
// 显示过滤后的WiFi扫描结果
private void showScanResult(List<ScanResult> list, Map<String, ScanResult> map) {
tv_result.setText(String.format("找到%d个WiFi热点,其中有%d个支持RTT。",
list.size(), map.size()));
lv_scan.setAdapter(new ScanListAdapter(this, list, map));
}
}
这个要求路由器具备RTT功能,还要求手机支持室内wifi定位 效果如下

代码如下
package com.example.location;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.RangingResultCallback;
import android.net.wifi.rtt.WifiRttManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.util.ArrayList;
import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.P)
public class WifiRttActivity extends AppCompatActivity {
private final static String TAG = "WifiRttActivity";
private TextView tv_result; // 声明一个文本视图对象
private WifiManager mWifiManager; // 声明一个WiFi管理器对象
private WifiScanReceiver mWifiScanReceiver = new WifiScanReceiver(); // 声明一个WiFi扫描接收器对象
private WifiRttManager mRttManager; // 声明一个RTT管理器对象
private WifiRttReceiver mWifiRttReceiver = new WifiRttReceiver(); // 声明一个RTT接收器对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wifi_rtt);
tv_result = findViewById(R.id.tv_result);
findViewById(R.id.btn_indoor_rtt).setOnClickListener(v -> mWifiManager.startScan());
// 从系统服务中获取WiFi管理器
mWifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
// 从系统服务中获取RTT管理器
mRttManager = (WifiRttManager) getSystemService(Context.WIFI_RTT_RANGING_SERVICE);
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_RTT)) {
tv_result.setText("当前设备支持室内WiFi定位");
} else {
tv_result.setText("当前设备不支持室内WiFi定位");
}
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filterScan = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
registerReceiver(mWifiScanReceiver, filterScan); // 注册Wifi扫描的广播接收器
IntentFilter filterRtt = new IntentFilter(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
registerReceiver(mWifiRttReceiver, filterRtt); // 注册RTT状态变更的广播接收器
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mWifiScanReceiver); // 注销Wifi扫描的广播接收器
unregisterReceiver(mWifiRttReceiver); // 注销RTT状态变更的广播接收器
}
// 定义一个扫描周边WiFi的广播接收器
private class WifiScanReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取WiFi扫描的结果列表
List<ScanResult> scanList = mWifiManager.getScanResults();
if (scanList != null) {
// 查找符合80211标准的WiFi路由器集合
List<ScanResult> m80211mcList = find80211mcResults(scanList);
runOnUiThread(() -> {
String desc = String.format("找到%d个Wifi热点,其中有%d个支持RTT。",
scanList.size(), m80211mcList.size());
tv_result.setText(desc);
});
if (m80211mcList.size() > 0) {
rangingRtt(m80211mcList.get(0)); // 测量与RTT节点之间的距离
}
}
}
}
// 查找符合80211标准的WiFi路由器集合
private List<ScanResult> find80211mcResults(List<ScanResult> originList) {
List<ScanResult> resultList = new ArrayList<>();
for (ScanResult scanResult : originList) { // 遍历扫描发现的WiFi列表
if (scanResult.is80211mcResponder()) { // 符合80211标准
resultList.add(scanResult);
}
}
return resultList;
}
// 测量与RTT节点之间的距离
private void rangingRtt(ScanResult scanResult) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
tv_result.setText("请先授予定位权限");
return;
}
RangingRequest.Builder builder = new RangingRequest.Builder();
builder.addAccessPoint(scanResult); // 添加测距入口,参数为ScanResult类型
// builder.addWifiAwarePeer(); // MacAddress类型
RangingRequest request = builder.build();
// 开始测量当前设备与指定RTT节点(路由器)之间的距离
mRttManager.startRanging(request, getMainExecutor(), new RangingResultCallback() {
// 测距失败时触发
@Override
public void onRangingFailure(int code) {
Log.d(TAG, "RTT扫描失败,错误代码为"+code);
}
// 测距成功时触发
@Override
public void onRangingResults(List<RangingResult> results) {
for (RangingResult result : results) {
if (result.getStatus() == RangingResult.STATUS_SUCCESS
&& scanResult.BSSID.equals(result.getMacAddress().toString())) {
result.getDistanceMm(); // 获取当前设备与路由器之间的距离(单位毫米)
result.getDistanceStdDevMm(); // 获取测量偏差(单位毫米)
result.getRangingTimestampMillis(); // 获取测量耗时(单位毫秒)
result.getRssi(); // 获取路由器的信号
}
}
}
});
}
private class WifiRttReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (mRttManager.isAvailable()) {
runOnUiThread(() -> tv_result.setText("室内WiFi定位功能可以使用"));
} else {
runOnUiThread(() -> tv_result.setText("室内WiFi定位功能不可使用"));
}
}
}
}
创作不易 觉得有帮助请点赞关注收藏~~~
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar
这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife
我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)
我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案
我需要检查DateTime是否采用有效的ISO8601格式。喜欢:#iso8601?我检查了ruby是否有特定方法,但没有找到。目前我正在使用date.iso8601==date来检查这个。有什么好的方法吗?编辑解释我的环境,并改变问题的范围。因此,我的项目将使用jsapiFullCalendar,这就是我需要iso8601字符串格式的原因。我想知道更好或正确的方法是什么,以正确的格式将日期保存在数据库中,或者让ActiveRecord完成它们的工作并在我需要时间信息时对其进行操作。 最佳答案 我不太明白你的问题。我假设您想检查
我的日期格式如下:"%d-%m-%Y"(例如,今天的日期为07-09-2015),我想看看是不是在过去的七天内。谁能推荐一种方法? 最佳答案 你可以这样做:require"date"Date.today-7 关于ruby-检查日期是否在过去7天内,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/32438063/
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否