基于NB-IoT的智慧路灯监控系统(手机应用开发)

基于NB-IoT的智慧路灯监控系统(手机应用开发)

2019年09月10日 12:29:35 不脱发的程序猿 阅读数 1038更多

分类专栏: NB-IoT项目实践

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/m0_38106923/article/details/100182460

目录

1、系统总体描述

2、使用技术

2.1、通用技术

2.2、核心技术

2.3、技术亮点

3、开发软件

3.1、开发软件

3.2、测试软件

3.3、打包发布软件

4、功能概述

4.1、系统导航

4.2、用户登录

4.3、扫码绑定

4.4、设备定位 

4.5、设备状态

4.6、历史查询

4.7、设备控制

4.8、画像分析

4.9、系统设置

5、核心代码

5.1、数据获取核心代码 

5.2、第三方SDK调用核心代码

5.3、图表数据展示核心代码

5.4、日历数据查询核心代码


通过与华为云平台进行数据对接及联动控制,为此开发智慧路灯APP控制系统。

1、系统总体描述

本系统共分为九个模块:系统导航、用户登录、扫码绑定、设备定位、设备状态、历史查询、设备控制、画像分析和系统设置。每个模块对应其各自的功能,通过设备的定位、设备的实时状态及设备控制能够全方位监控路灯的耗能量及使用情况。画像分析也可对某地方或某用户进行大数据AI分析得到监测数据,并且能够实时向用户推送用电情况,并为其用户进行合理的用电安排及方案。

2、使用技术

2.1、通用技术

  • 系统总体使用java语言进行开发;
  • 在界面设计及展示部分使用HTML搭配CSS技术使其界面美观大方;
  • 框架设计使用MVP模式进行设计使其系统结构清晰明了;
  • 数据对接使用HTTP和OkHttp3协议,大大降低数据处理难度;且提高了数据的完整性和实时性。

2.2、核心技术

  • 登录界面使用视屏背景技术将登录界面进行高度美化。
  • 在设备定位模块中使用第三方高德地图SDK进行开发;
  • 在云平台对接时使用华为云平台相关模块接口进行开发;
  • 使用Clendar相关类进行日期选择设计;
  • 使用Zxing二维码扫描分析技术进行扫码分析;
  • 使用Echart技术进行数据实时显示图表分析;
  • 在画像分析模块使用AI大数据分析获取数据实例。

2.3、技术亮点

  • 对第三方技术的合理运用;
  • 对MVP开发框架的组合设计;
  • 对API接口的清晰掌握;
  • 对各种相关工具类的开发及调用;
  • 结合大数据AI分析进行功能设计。

3、开发软件

3.1、开发软件

  • 系统环境:Windows 10
  • 开发环境:Android Studio 3.0,JDK 8.0
  • 运行环境:Android 5.0级以上

3.2、测试软件

  • 接口测试软件:Postman 6.5

3.3、打包发布软件

  • 版本控制软件:Git
  • 打包发布软件:Android Studio (Generate Signed  APK)
  • 软件签名:iot_project.jks

4、功能概述

4.1、系统导航

首次进入APP当进入导航界面,导航界面中介绍APP的Logo、简单描述、路灯模型、路灯功能分类及路灯运行方式。效果如下所示:

4.2、用户登录

用户登录界面使用视屏作为页面背景,通过输入用户名及密码进行系统登录。系统的用户名及密码在系统后台统一进行注册。效果如下所示:

4.3、扫码绑定

用户登录成功后将自动跳转至扫码界面,跳转界面后会对该移动设备进行权限访问,用户需要同意所有权限才能正常使用该系统。授权后进行二维码扫描。此时需要对路灯上的二维码进行扫码,通过扫码得到该路灯的设备信息,从而在主界面中可查看该路灯的其他信息。 效果如下所示:

4.4、设备定位 

此模块中将对该扫描设备进行设备定位,观察其设备所在的具体位置,并能够查看当前地方的天气环境。此处的设计也是为后来的管理方便,对每一个路灯设备能够全方位的进行查看。效果如下所示:

4.5、设备状态

此模块将对所在设备的所有信息进行实时查看,有电压、电流、功率、功率因子、总耗电量、光照度、路灯开光状态及路灯耗能所产生的二氧化碳量。 效果如下所示:

4.6、历史查询

此模块是对该路灯所有数据的历史查询,通过对历史数据的查询可分析出该设备在本周、本月及本年的所有用电量情况。这样就能够合理的对路灯用电量进行管理。效果如下所示:

4.7、设备控制

此模块是对路灯的远程控制,共分为三个模式分别为:终端联控模式、分段定时模式及自动调光模式。三种模式分别对应三种不同的路灯控制,可远程也可自动,充分达到了用电量的控制。效果如下所示:

4.8、画像分析

此模块涉及了大数据AI分析功能,将分析的数据下发至该系统,系统对其数据进行图文的可视化展示,清晰的可以查看到该使用者日常用电情况及地方用电情况。(由于数据集较少,建立的模型是我本人2019年7月份路灯节点使用状况) 效果如下所示:

4.9、系统设置

系统设置功能共分为以下几点:系统设置、修改密码、关于我们、系统更新及退出登录。效果如下所示:

5、核心代码

5.1、数据获取核心代码 

 
  1. public class NetConnectHeaderDataJSON {

  2. public static final String REQUEST_TYPE_GET = "GET";

  3. public static final String REQUEST_TYPE_POST = "POST";

  4. public static final String REQUEST_TYPE_PUT = "PUT";

  5. /**

  6. * 请求URL并返回内容

  7. *

  8. * @param method

  9. * 方式get post

  10. * @param url

  11. * 地址

  12. * @param param

  13. * 参数

  14. * @return json

  15. */

  16. public static String request(Context context, String method, String url, String app_id, String token,

  17. List<NameValuePair> param) throws Exception {

  18. HttpResponse response;

  19. String result_https="";

  20. if (method.equals(REQUEST_TYPE_GET)){

  21. String ps ="";

  22. if (param != null) {

  23. List<NameValuePair> param2 = new ArrayList<NameValuePair>();

  24. for (int i = 0; i < param.size(); i++) {

  25. if (i>0) ps+="&";

  26. String key = param.get(i).getName();

  27. String value =param.get(i)

  28. .getValue();

  29. ps+=key+"="+value;

  30. }

  31. ps = URLEncodedUtils.format(param2, HTTP.UTF_8);

  32. // 通过url创建对象

  33. if (url.indexOf("?") > 0) {

  34. url += "&" + ps;

  35. } else {

  36. url += "?" + ps;

  37. }

  38. }

  39. SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);

  40.  
  41. HttpClient httpClient = new DefaultHttpClient();

  42. if (ssl != null) {

  43. Scheme sch = new Scheme("https", ssl, 443);

  44. httpClient.getConnectionManager().getSchemeRegistry().register(sch);

  45. }

  46. HttpGet request = new HttpGet(url);

  47. request.setHeader("app_key",app_id);

  48. request.setHeader("Authorization","Bearer "+token);

  49. request.setHeader("Content-Type","application/json");

  50. // request.setEntity(new UrlEncodedFormEntity(param));

  51. // 发起请求,获取回应,自封装接口,详见附录

  52. response = httpClient.execute(request);

  53. HttpEntity httpEntity = response.getEntity();

  54. // 得到一些数据

  55. // 通过EntityUtils并指定编码方式取到返回的数据

  56. StatusLine statusLine = response.getStatusLine();

  57.  
  58. statusLine.getProtocolVersion();

  59. int statusCode = statusLine.getStatusCode();

  60. if (statusCode == 200) {

  61. result_https = (EntityUtils.toString(httpEntity, "utf-8"));

  62. }else{

  63. result_https=""+statusCode;

  64. }

  65.  
  66. }else if(method.equals(REQUEST_TYPE_POST)){

  67. SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);

  68.  
  69. HttpClient httpClient = new DefaultHttpClient();

  70. if (ssl != null) {

  71. Scheme sch = new Scheme("https", ssl, 443);

  72. httpClient.getConnectionManager().getSchemeRegistry().register(sch);

  73. }

  74. HttpPost request = new HttpPost(url);

  75. if (param != null) {

  76. String JsonData="";

  77. String startdata="{";

  78. String enddata="}";

  79. for (int i = 0; i < param.size(); i++) {

  80. String key = param.get(i).getName();

  81. String values = param.get(i).getValue();

  82. // param2.add(new BasicNameValuePair(key, values));

  83. if (i>0)

  84. JsonData+=",";

  85. if (key.equals("timeout"))

  86. JsonData+="\""+key+"\":"+values;

  87. else

  88. JsonData+="\""+key+"\":\""+values+"\"";

  89. }

  90. //传入的是json格式的数据

  91. JsonData=startdata+JsonData+enddata;

  92. request.setEntity(new StringEntity(JsonData, HTTP.UTF_8));

  93. }

  94. request.setHeader("app_key",app_id);

  95. request.setHeader("Authorization","Bearer "+token);

  96. request.setHeader("Content-Type","application/json");

  97. response = httpClient.execute(request);

  98. HttpEntity httpEntity = response.getEntity();

  99. // 通过EntityUtils并指定编码方式取到返回的数据

  100. StatusLine statusLine = response.getStatusLine();

  101. int statusCode = statusLine.getStatusCode();

  102. if (statusCode == 200) {

  103. result_https = (EntityUtils.toString(httpEntity, "utf-8"));

  104. }else{

  105. result_https=""+statusCode;

  106. }

  107. }else if(method.equals(REQUEST_TYPE_PUT)){

  108. SSLSocketFactory ssl=DataManager.GETSSLinitHttpClientBook(context);

  109. HttpClient httpClient = new DefaultHttpClient();

  110. if (ssl != null) {

  111. Scheme sch = new Scheme("https", ssl, 443);

  112. httpClient.getConnectionManager().getSchemeRegistry().register(sch);

  113. }

  114. HttpPut request = new HttpPut(url);

  115. if (param != null) {

  116. String JsonData="";

  117. String startdata="{";

  118. String enddata="}";

  119. for (int i = 0; i < param.size(); i++) {

  120. String key = param.get(i).getName();

  121. String values = param.get(i).getValue();

  122. if (i>0)

  123. JsonData+=",";

  124. if (key.equals("timeout"))

  125. JsonData+="\""+key+"\":"+values;

  126. else

  127. JsonData+="\""+key+"\":\""+values+"\"";

  128. }

  129. //传入的是json格式的数据

  130. JsonData=startdata+JsonData+enddata;

  131. request.setEntity(new StringEntity(JsonData, HTTP.UTF_8));

  132. }

  133. //

  134. request.setHeader("app_key",app_id);

  135. request.setHeader("Authorization","Bearer "+token);

  136. request.setHeader("Content-Type","application/json");

  137. response = httpClient.execute(request);

  138. HttpEntity httpEntity = response.getEntity();

  139. // 通过EntityUtils并指定编码方式取到返回的数据

  140. StatusLine statusLine = response.getStatusLine();

  141. int statusCode = statusLine.getStatusCode();

  142. if (statusCode == 204) {

  143. result_https = (EntityUtils.toString(httpEntity, "utf-8"));

  144. }else{

  145. result_https=""+statusCode;

  146. }

  147. }

  148. else{

  149. Log.i("method==", ".....");

  150. }

  151. return result_https;

  152. }

  153. }

5.2、第三方SDK调用核心代码

 
  1. /**

  2. * 方法必须重写

  3. */

  4. @Override

  5. public void onResume() {

  6. super.onResume();

  7. mapView.onResume();

  8. }

  9. /**

  10. * 方法必须重写

  11. */

  12. @Override

  13. public void onPause() {

  14. super.onPause();

  15. mapView.onPause();

  16. }

  17. /**

  18. * 方法必须重写

  19. */

  20. @Override

  21. public void onSaveInstanceState(Bundle outState) {

  22. super.onSaveInstanceState(outState);

  23. mapView.onSaveInstanceState(outState);

  24. }

  25. /**

  26. * 方法必须重写

  27. */

  28. @Override

  29. public void onDestroy() {

  30. super.onDestroy();

  31. mapView.onDestroy();

  32. if (mTimerTask != null) {

  33. mTimerTask.cancel();

  34. mTimerTask = null;

  35. }

  36. try {

  37. mTimer.cancel();

  38. } catch (Throwable e) {

  39. e.printStackTrace();

  40. }

  41. deactivate();

  42. }

  43. @Override

  44. public void onMapClick(LatLng latLng) {

  45.  
  46. }

  47. @Override

  48. public void onMapLoaded() {

  49. aMap.moveCamera(CameraUpdateFactory.zoomTo(zoomLevel));

  50. }

  51. @Override

  52. public void activate(OnLocationChangedListener onLocationChangedListener) {

  53. mListener = onLocationChangedListener;

  54. startlocation();

  55. }

  56. @Override

  57. public void deactivate() {

  58. mListener = null;

  59. if (mLocationClient != null) {

  60. mLocationClient.stopLocation();

  61. mLocationClient.onDestroy();

  62. }

  63. mLocationClient = null;

  64. }

  65. /**

  66. * 开始定位。

  67. */

  68. private void startlocation() {

  69.  
  70. if (mLocationClient == null) {

  71. mLocationClient = new AMapLocationClient(getActivity());

  72. mLocationOption = new AMapLocationClientOption();

  73. // 设置定位监听

  74. mLocationClient.setLocationListener(this);

  75. // 设置为高精度定位模式

  76. mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);

  77. //设置为单次定位

  78. mLocationOption.setNeedAddress(true);

  79. mLocationOption.setOnceLocation(true);

  80. // 设置定位参数

  81. mLocationClient.setLocationOption(mLocationOption);

  82. mLocationClient.startLocation();

  83. } else {

  84. mLocationClient.startLocation();

  85. }

  86. }

  87. @Override

  88. public void onLocationChanged(AMapLocation aMapLocation) {

  89. if (mListener != null && aMapLocation != null) {

  90. if (mTimerTask != null) {

  91. mTimerTask.cancel();

  92. mTimerTask = null;

  93. }

  94. if (aMapLocation != null && aMapLocation.getErrorCode() == 0) {

  95. LatLng mylocation = new LatLng(Absoult.Latitude, Absoult.Longitude);

  96. changeCamera(CameraUpdateFactory.newLatLngZoom(mylocation,zoomLevel),null);

  97. SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  98. Date date = new Date(aMapLocation.getTime());

  99. mTextViewCity.setText(aMapLocation.getCountry()+aMapLocation.getProvince()+aMapLocation.getCity());

  100. mTextViewAddress.setText(aMapLocation.getAddress());

  101. cityName = aMapLocation.getCity().substring(0,aMapLocation.getCity().length()-1);

  102. initWaetherData(cityName);

  103. aMapLocation.getAddress();//地址,如果option中设置isNeedAddress为false,则没有此结果,网络定位结果中会有地址信息,GPS定位不返回地址信息。

  104. aMapLocation.getCountry();//国家信息

  105. aMapLocation.getProvince();//省信息

  106. aMapLocation.getCity();//城市信息

  107. aMapLocation.getDistrict();//城区信息

  108. aMapLocation.getStreet();//街道信息

  109. aMapLocation.getStreetNum();//街道门牌号信息

  110. aMapLocation.getCityCode();//城市编码

  111. aMapLocation.getAdCode();//地区编码

  112. df.format(date);//定位时间

  113. if (locMarker==null){

  114. addLocationMarker(mylocation);

  115. }else {

  116. locMarker.setPosition(mylocation);

  117. }

  118. if (ac!=null){

  119. ac.setCenter(mylocation);

  120. }

  121. if (c!=null){

  122. c.setCenter(mylocation);

  123. }

  124. if (d!=null){

  125. d.setCenter(mylocation);

  126. }

  127. } else {

  128. String errText = "定位失败," + aMapLocation.getErrorCode() + ": "

  129. + aMapLocation.getErrorInfo();

  130. Log.e("AmapErr", errText);

  131. }

  132. }

  133. }

  134. /**

  135. * 添加坐标点,这里可以添加任意坐标点位置

  136. * @param mylocation

  137. */

  138. private void addLocationMarker(LatLng mylocation) {

  139. float accuracy = (float) ((mylocation.longitude/mylocation.latitude ));

  140. if (locMarker == null) {

  141. locMarker = addMarker(mylocation);

  142. if (ac==null){

  143. ac = aMap.addCircle(new CircleOptions().center(mylocation)

  144. .fillColor(Color.argb(0, 98 ,189, 255)).radius(accuracy)

  145. .strokeColor(Color.argb(0, 98, 198, 255)).strokeWidth(0));

  146. }

  147. if (c==null){

  148. c = aMap.addCircle(new CircleOptions().center(mylocation)

  149. .fillColor(Color.argb(0, 98, 198, 255))

  150. .radius(accuracy).strokeColor(Color.argb(0,98, 198, 255))

  151. .strokeWidth(0));

  152. }

  153. if (d==null){

  154. d = aMap.addCircle(new CircleOptions().center(mylocation)

  155. .fillColor(Color.argb(0, 98, 198, 255))

  156. .radius(accuracy).strokeColor(Color.argb(0,98, 198, 255))

  157. .strokeWidth(0));

  158. }

  159. } else {

  160. locMarker.setPosition(mylocation);

  161. ac.setCenter(mylocation);

  162. ac.setRadius(accuracy);

  163. c.setCenter(mylocation);

  164. c.setRadius(accuracy);

  165. d.setCenter(mylocation);

  166. d.setRadius(accuracy);

  167. }

  168. handle.postDelayed(rb,0);

  169. handle1.postDelayed(rb1,800);

  170. handle2.postDelayed(rb2,1600);

  171. }

  172. /**

  173. * 位置波纹扩散动画

  174. * @param ac

  175. */

  176. private void Scalecircle1(final Circle ac) {

  177. ValueAnimator vm = ValueAnimator.ofFloat(0,(float)ac.getRadius());

  178. vm.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  179. @Override

  180. public void onAnimationUpdate(ValueAnimator animation) {

  181. float curent = (float) animation.getAnimatedValue();

  182. ac.setRadius(curent);

  183. aMap.invalidate();

  184. }

  185. });

  186. ValueAnimator vm1 = ValueAnimator.ofInt(160,0);

  187. vm1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  188. @Override

  189. public void onAnimationUpdate(ValueAnimator animation) {

  190. int color = (int) animation.getAnimatedValue();

  191. ac.setFillColor(Color.argb(color, 98, 198, 255));

  192. aMap.invalidate();

  193. }

  194. });

  195. vm.setRepeatCount(Integer.MAX_VALUE);

  196. vm.setRepeatMode(ValueAnimator.RESTART);

  197. vm1.setRepeatCount(Integer.MAX_VALUE);

  198. vm1.setRepeatMode(ValueAnimator.RESTART);

  199. AnimatorSet set = new AnimatorSet();

  200. set.play(vm).with(vm1);

  201. set.setDuration(2500);

  202. set.setInterpolator(interpolator1);

  203. set.start();

  204. }

  205. private final Interpolator interpolator1 = new LinearInterpolator();

  206. Runnable rb = new Runnable() {

  207. @Override

  208. public void run() {

  209. Scalecircle1(ac);

  210. }

  211. };

  212. Handler handle =new Handler();

  213. Runnable rb1 = new Runnable() {

  214. @Override

  215. public void run() {

  216. Scalecircle1(c);

  217. }

  218. };

  219. Handler handle1 =new Handler();

  220. Runnable rb2 = new Runnable() {

  221. @Override

  222. public void run() {

  223. Scalecircle1(d);

  224. }

  225. };

  226. Handler handle2 =new Handler();

5.3、图表数据展示核心代码

 
  1. public class ChartService {

  2. private GraphicalView mGraphicalView;

  3. private XYMultipleSeriesDataset multipleSeriesDataset;// 数据集容器

  4. private XYMultipleSeriesRenderer multipleSeriesRenderer;// 渲染器容器

  5. private XYSeries mSeries;// 单条曲线数据集

  6. private XYSeriesRenderer mRenderer;// 单条曲线渲染器

  7. private Context context;

  8. private Double xx ;

  9. int aa = 100;

  10. public ChartService(Context context) {

  11. this.context = context;

  12. }

  13. /**

  14. * 获取图表

  15. *

  16. * @return

  17. */

  18. public GraphicalView getGraphicalView() {

  19. mGraphicalView = ChartFactory.getCubeLineChartView(context,

  20. multipleSeriesDataset, multipleSeriesRenderer, 0.1f);

  21. return mGraphicalView;

  22. }

  23.  
  24. /**

  25. * 获取数据集,及xy坐标的集合

  26. *

  27. * @param curveTitle

  28. */

  29. public void setXYMultipleSeriesDataset(String curveTitle) {

  30. multipleSeriesDataset = new XYMultipleSeriesDataset();

  31. mSeries = new XYSeries(curveTitle);

  32. multipleSeriesDataset.addSeries(mSeries);

  33. }

  34. /**

  35. * 获取渲染器

  36. *

  37. * @param maxX

  38. * x轴最大值

  39. * @param maxY

  40. * y轴最大值

  41. * @param chartTitle

  42. * 曲线的标题

  43. * @param xTitle

  44. * x轴标题

  45. * @param yTitle

  46. * y轴标题

  47. * @param axeColor

  48. * 坐标轴颜色

  49. * @param labelColor

  50. * 标题颜色

  51. * @param curveColor

  52. * 曲线颜色

  53. * @param gridColor

  54. * 网格颜色

  55. */

  56. public void setXYMultipleSeriesRenderer(double maxX, double maxY,

  57. String chartTitle, String xTitle, String yTitle, int axeColor,

  58. int labelColor, int curveColor, int gridColor) {

  59. multipleSeriesRenderer = new XYMultipleSeriesRenderer();

  60. if (chartTitle != null) {

  61. multipleSeriesRenderer.setChartTitle(chartTitle);

  62. }

  63. multipleSeriesRenderer.setXTitle(xTitle);

  64. multipleSeriesRenderer.setYTitle(yTitle);

  65. multipleSeriesRenderer.setRange(new double[] { 0, maxX, 0, maxY });//xy轴的范围

  66. multipleSeriesRenderer.setLabelsColor(labelColor);

  67. multipleSeriesRenderer.setXLabels(5);

  68. multipleSeriesRenderer.setYLabels(10);

  69. multipleSeriesRenderer.setXLabelsAlign(Align.RIGHT);

  70. multipleSeriesRenderer.setYLabelsAlign(Align.RIGHT);

  71. multipleSeriesRenderer.setAxisTitleTextSize(35);

  72. multipleSeriesRenderer.setChartTitleTextSize(35);

  73. multipleSeriesRenderer.setLabelsTextSize(35);

  74. multipleSeriesRenderer.setLegendTextSize(35);

  75. multipleSeriesRenderer.setPointSize(5f);//曲线描点尺寸

  76. multipleSeriesRenderer.setFitLegend(true);

  77. multipleSeriesRenderer.setMargins(new int[] { 80, 80, 80, 80 });

  78. multipleSeriesRenderer.setShowGrid(true);

  79. multipleSeriesRenderer.setPanEnabled(false, false);//允许X轴可拉动

  80. multipleSeriesRenderer.setZoomEnabled(false, false);

  81. multipleSeriesRenderer.setAxesColor(axeColor);

  82. multipleSeriesRenderer.setGridColor(gridColor);

  83. multipleSeriesRenderer.setBackgroundColor(Color.WHITE);//背景色

  84. multipleSeriesRenderer.setMarginsColor(Color.WHITE);//边距背景色,默认背景色为黑色,这里修改为白色

  85. mRenderer = new XYSeriesRenderer();

  86. mRenderer.setLineWidth(3f);

  87. mRenderer.setColor(curveColor);

  88. mRenderer.setPointStrokeWidth(5f);

  89. mRenderer.setPointStyle(PointStyle.CIRCLE);//描点风格,可以为圆点,方形点等等

  90. multipleSeriesRenderer.addSeriesRenderer(mRenderer);

  91. }

  92.  
  93. /**

  94. * 根据新加的数据,更新曲线,只能运行在主线程

  95. *

  96. * @param x

  97. * 新加点的x坐标

  98. * @param y

  99. * 新加点的y坐标

  100. */

  101. public void updateChart(double x, double y) {

  102. mSeries.add(x, y);

  103. if (x>aa) {

  104. aa += 1;

  105. multipleSeriesRenderer.setXAxisMax(aa);// 设置X最大值

  106. multipleSeriesRenderer.setXAxisMin(aa - 100);// 设置X最小值

  107. }

  108. mGraphicalView.repaint();//此处也可以调用invalidate()

  109. }

  110. /**

  111. * 添加新的数据,多组,更新曲线,只能运行在主线程

  112. * @param xList

  113. * @param yList

  114. */

  115. public void updateChart(List<Double> xList, List<Double> yList) {

  116. for (int i = 0; i < xList.size(); i++) {

  117. xx = xList.get(i);

  118. mSeries.add(xx, yList.get(i));

  119. }

  120. mGraphicalView.repaint();//此处也可以调用invalidate()

  121. }

  122. }

5.4、日历数据查询核心代码

 
  1. public abstract class NCalendar extends FrameLayout implements NestedScrollingParent, OnCalendarStateChangedListener, OnDateChangedListener, OnMonthAnimatorListener {

  2. protected WeekCalendar weekCalendar;

  3. protected MonthCalendar monthCalendar;

  4. protected int weekHeight;//周日历的高度

  5. protected int monthHeight;//月日历的高度,是日历整个的高

  6. protected int childLayoutLayoutTop;//onLayout中,定位的高度,

  7. protected int STATE;//默认月

  8. private int lastSate;//防止状态监听重复回调

  9. private OnCalendarChangedListener onCalendarChangedListener;

  10. //NCalendar内部包含的直接子view,直接子view并不一定是NestScrillChild

  11. protected ChildLayout childLayout; protected Rect monthRect;//月日历大小的矩形

  12. protected Rect weekRect;//周日历大小的矩形 ,用于判断点击事件是否在日历的范

  13. private boolean isWeekHold;//是否需要周状态定住

  14. public NCalendar(@NonNull Context context) {

  15. this(context, null);

  16. }

  17.  
  18. public NCalendar(@NonNull Context context, @Nullable AttributeSet attrs) {

  19. this(context, attrs, 0);

  20. }

  21. public NCalendar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

  22. super(context, attrs, defStyleAttr);

  23. setMotionEventSplittingEnabled(false);

  24. Attrs attrss = AttrsUtil.getAttrs(context, attrs);

  25. int duration = attrss.duration;

  26. monthHeight = attrss.monthCalendarHeight;

  27. STATE = attrss.defaultCalendar;

  28. weekHeight = monthHeight / 5;

  29. isWeekHold = attrss.isWeekHold;

  30. weekCalendar = new WeekCalendar(context, attrss);

  31. monthCalendar = new MonthCalendar(context, attrss, duration, this);

  32. childLayout = new ChildLayout(getContext(), attrs, monthHeight, duration, this);

  33. monthCalendar.setOnDateChangedListener(this);

  34. weekCalendar.setOnDateChangedListener(this);

  35. childLayout.setBackgroundColor(attrss.bgChildColor);

  36. setCalenadrState(STATE);

  37. childLayoutLayoutTop = STATE == Attrs.WEEK ? weekHeight : monthHeight;

  38. post(new Runnable() {

  39. @Override

  40. public void run() {

  41.  
  42. monthRect = new Rect(0, 0, monthCalendar.getWidth(), monthCalendar.getHeight());

  43. weekRect = new Rect(0, 0, weekCalendar.getWidth(), weekCalendar.getHeight());

  44.  
  45. monthCalendar.setY(STATE == Attrs.MONTH ? 0 : getMonthYOnWeekState());

  46. childLayout.setY(STATE == Attrs.MONTH ? monthHeight : weekHeight);

  47. }

  48. });

  49. }

  50.  
  51. /**

  52. * 根据ChildLayout的自动滑动结束的状态来设置月周日历的状态

  53. * 依据ChildLayout的状态来设置日历的状态

  54. *

  55. * @param isMonthState

  56. */

  57. @Override

  58. public void onCalendarStateChanged(boolean isMonthState) {

  59. if (isMonthState) {

  60. setCalenadrState(Attrs.MONTH);

  61. } else {

  62. setCalenadrState(Attrs.WEEK);

  63. }

  64. }

  65.  
  66. /**

  67. * xml文件加载结束,添加月,周日历和child到NCalendar中

  68. */

  69. @Override

  70. protected void onFinishInflate() {

  71. super.onFinishInflate();

  72.  
  73. if (getChildCount() != 1) {

  74. throw new RuntimeException("NCalendar中的只能有一个直接子view");

  75. }

  76. childLayout.addView(getChildAt(0), new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

  77.  
  78. addView(monthCalendar, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, monthHeight));

  79. addView(weekCalendar, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, weekHeight));

  80. addView(childLayout, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

  81. }

  82. @Override

  83. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  84. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  85. ViewGroup.LayoutParams childLayoutLayoutParams = childLayout.getLayoutParams();

  86. childLayoutLayoutParams.height = getMeasuredHeight() - weekHeight;

  87.  
  88. //需要再调一次父类的方法?真机不调用首次高度不对,为何?

  89. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

  90. }

  91. @Override

  92. protected void onLayout(boolean changed, int l, int t, int r, int b) {

  93. //super.onLayout(changed, l, t, r, b); //调用父类的该方法会造成 快速滑动月日历同时快速上滑recyclerview造成月日历的残影

  94.  
  95. int measuredWidth = getMeasuredWidth();

  96. weekCalendar.layout(0, 0, measuredWidth, weekHeight);

  97. monthCalendar.layout(0, 0, measuredWidth, monthHeight);

  98. childLayout.layout(0, childLayoutLayoutTop, measuredWidth, childLayout.getMeasuredHeight() + childLayoutLayoutTop);

  99. }

  100. /**

  101. * 根据条件设置日历的月周状态,并回调状态变化

  102. *

  103. * @param state

  104. */

  105. private void setCalenadrState(int state) {

  106.  
  107. if (state == Attrs.WEEK) {

  108. STATE = Attrs.WEEK;

  109. weekCalendar.setVisibility(VISIBLE);

  110. } else {

  111. STATE = Attrs.MONTH;

  112. weekCalendar.setVisibility(INVISIBLE);

  113. }

  114.  
  115. if (onCalendarChangedListener != null && lastSate != state) {

  116. onCalendarChangedListener.onCalendarStateChanged(STATE == Attrs.MONTH);

  117. }

  118.  
  119. lastSate = state;

  120. }

  121. /**

  122. * 自动滑动到适当的位置

  123. */

  124. private void autoScroll() {

  125. float childLayoutY = childLayout.getY();

  126. if (STATE == Attrs.MONTH && monthHeight - childLayoutY < weekHeight) {

  127. onAutoToMonthState();

  128. } else if (STATE == Attrs.MONTH && monthHeight - childLayoutY >= weekHeight) {

  129. onAutoToWeekState();

  130. } else if (STATE == Attrs.WEEK && childLayoutY < weekHeight * 2) {

  131. onAutoToWeekState();

  132. } else if (STATE == Attrs.WEEK && childLayoutY >= weekHeight * 2) {

  133. onAutoToMonthState();

  134. }

  135. }

  136. /**

  137. * 月日历和周日历的日期变化回调,每次日期变化都会回调,用于不同状态下,设置另一个日历的日期

  138. *

  139. * @param baseCalendar 日历本身

  140. * @param localDate 当前选中的时间

  141. * @param isDraw 是否绘制 此处选择都绘制,默认不选中,不适用鱼月周切换

  142. */

  143. @Override

  144. public void onDateChanged(BaseCalendar baseCalendar, LocalDate localDate, boolean isDraw) {

  145. if (baseCalendar instanceof MonthCalendar && STATE == Attrs.MONTH) {

  146. //月日历变化,改变周的选中

  147. weekCalendar.jumpDate(localDate, true);

  148. if (onCalendarChangedListener != null) {

  149. onCalendarChangedListener.onCalendarDateChanged(Util.getNDate(localDate));

  150. }

  151. } else if (baseCalendar instanceof WeekCalendar && STATE == Attrs.WEEK) {

  152. //周日历变化,改变月的选中

  153. monthCalendar.jumpDate(localDate, true);

  154. post(new Runnable() {

  155. @Override

  156. public void run() {

  157. //此时需要根据月日历的选中日期调整Y值

  158. // post是因为在前面得到当前view是再post中完成,如果不这样直接获取位置信息,会出现老的数据,不能获取正确的数据

  159. monthCalendar.setY(getMonthYOnWeekState());

  160. }

  161. });

  162. if (onCalendarChangedListener != null) {

  163. onCalendarChangedListener.onCalendarDateChanged(Util.getNDate(localDate));

  164. }

  165. }

  166. }

  167. @Override

  168. public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {

  169. return true;

  170. }

  171. @Override

  172. public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {

  173. //跟随手势滑动

  174. gestureMove(dy, consumed);

  175. }

  176. @Override

  177. public boolean onNestedPreFling(View target, float velocityX, float velocityY) {

  178. //只有都在都在周状态下,才允许子View Fling滑动

  179. return !(childLayout.isWeekState() && monthCalendar.isWeekState());

  180. }

  181. @Override

  182. public void onStopNestedScroll(View target) {

  183. //该方法手指抬起的时候回调,此时根据此刻的位置,自动滑动到相应的状态,

  184. //如果已经在对应的位置上,则不执行动画,

  185. if (monthCalendar.isMonthState() && childLayout.isMonthState() && STATE == Attrs.WEEK) {

  186. setCalenadrState(Attrs.MONTH);

  187. } else if (monthCalendar.isWeekState() && childLayout.isWeekState() && STATE == Attrs.MONTH) {

  188. setCalenadrState(Attrs.WEEK);

  189. } else if (!childLayout.isMonthState() && !childLayout.isWeekState()) {

  190. //不是周状态也不是月状态时,自动滑动

  191. autoScroll();

  192. }

  193. }

  194. /**

  195. * 手势滑动的逻辑,做了简单处理,2种状态,都以ChildLayout滑动的状态判断

  196. * 1、向上滑动未到周状态

  197. * 2、向下滑动未到月状态

  198. *

  199. * @param dy

  200. * @param consumed

  201. */

  202. protected void gestureMove(int dy, int[] consumed) {

  203. float monthCalendarY = monthCalendar.getY();

  204. float childLayoutY = childLayout.getY();

  205. if (dy > 0 && !childLayout.isWeekState()) {

  206. monthCalendar.setY(-getGestureMonthUpOffset(dy) + monthCalendarY);

  207. childLayout.setY(-getGestureChildUpOffset(dy) + childLayoutY);

  208. if (consumed != null) consumed[1] = dy;

  209. } else if (dy < 0 && isWeekHold && childLayout.isWeekState()) {

  210. //不操作,

  211. } else if

  212. (dy < 0 && !childLayout.isMonthState() && !childLayout.canScrollVertically(-1)) {

  213. monthCalendar.setY(getGestureMonthDownOffset(dy) + monthCalendarY);

  214. childLayout.setY(getGestureChildDownOffset(dy) + childLayoutY);

  215. if (consumed != null) consumed[1] = dy;

  216. }

  217.  
  218. onSetWeekVisible(dy);

  219. }

  220. /**

  221. * 月日历执行自动滑动动画的回调

  222. * 用来控制周日历的显示还是隐藏

  223. *

  224. * @param offset

  225. */

  226. @Override

  227. public void onMonthAnimatorChanged(int offset) {

  228. onSetWeekVisible(offset);

  229. }

  230. private int dowmY;

  231. private int downX;

  232. private int lastY;//上次的y

  233. private int verticalY = 50;//竖直方向上滑动的临界值,大于这个值认为是竖直滑动

  234. private boolean isFirstScroll = true; //第一次手势滑动,因为第一次滑动的偏移量大于verticalY,会出现猛的一划,这里只对第一次滑动做处理

  235. @Override

  236. public boolean onInterceptTouchEvent(MotionEvent ev) {

  237. switch (ev.getAction()) {

  238. case MotionEvent.ACTION_DOWN:

  239. dowmY = (int) ev.getY();

  240. downX = (int) ev.getX();

  241. lastY = dowmY;

  242. break;

  243. case MotionEvent.ACTION_MOVE:

  244. int y = (int) ev.getY();

  245. int absY = Math.abs(dowmY - y);

  246. boolean inCalendar = isInCalendar(downX, dowmY);

  247. if (absY > verticalY && inCalendar) {

  248. //onInterceptTouchEvent返回true,触摸事件交给当前的onTouchEvent处理

  249. return true;

  250. }

  251. break;

  252. }

  253. return super.onInterceptTouchEvent(ev);

  254. }

  255. @Override

  256. public boolean onTouchEvent(MotionEvent event) {

  257. switch (event.getAction()) {

  258. case MotionEvent.ACTION_MOVE:

  259. int y = (int) event.getY();

  260. int dy = lastY - y;

  261. if (isFirstScroll) {

  262. // 防止第一次的偏移量过大

  263. if (dy > verticalY) {

  264. dy = dy - verticalY;

  265. } else if (dy < -verticalY) {

  266. dy = dy + verticalY;

  267. }

  268. isFirstScroll = false;

  269. }

  270. // 跟随手势滑动

  271. gestureMove(dy, null);

  272. lastY = y;

  273. break;

  274. case MotionEvent.ACTION_UP:

  275. case MotionEvent.ACTION_CANCEL:

  276. isFirstScroll = true;

  277. autoScroll();

  278. break;

  279. }

  280. return true;

  281. }

  282. /**

  283. * 点击事件是否在日历的范围内

  284. *

  285. * @param x

  286. * @param y

  287. * @return

  288. */

  289. private boolean isInCalendar(int x, int y) {

  290. if (STATE == Attrs.MONTH) {

  291. return monthRect.contains(x, y);

  292. } else {

  293. return weekRect.contains(x, y);

  294. }

  295. }

  296. /**

  297. * 滑动过界处理 ,如果大于最大距离就返回最大距离

  298. *

  299. * @param offset 当前滑动的距离

  300. * @param maxOffset 当前滑动的最大距离

  301. * @return

  302. */

  303. protected float getOffset(float offset, float maxOffset) {

  304. if (offset > maxOffset) {

  305. return maxOffset;

  306. }

  307. return offset;

  308. }

  309. /**

  310. * 自动回到月的状态 包括月日历和chilayout

  311. */

  312. protected abstract void onAutoToMonthState();

  313. /**

  314. * 自动回到周的状态 包括月日历和chilayout

  315. */

  316. protected abstract void onAutoToWeekState();

  317.  
  318. /**

  319. * 设置weekCalendar的显示隐藏,该方法会在手势滑动和自动滑动的的时候一直回调

  320. */

  321. protected abstract void onSetWeekVisible(int dy);

  322. /**

  323. * 周状态下 月日历的getY 是个负值

  324. * 用于在 周状态下日期改变设置正确的y值

  325. *

  326. * @return

  327. */

  328. protected abstract float getMonthYOnWeekState();

  329. /**

  330. * 月日历根据手势向上移动的距离

  331. *

  332. * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动

  333. * @return 根据不同日历的交互,计算不同的滑动值

  334. */

  335. protected abstract float getGestureMonthUpOffset(int dy);

  336. /**

  337. * Child根据手势向上移动的距离

  338. *

  339. * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动

  340. * @return 根据不同日历的交互,计算不同的滑动值

  341. */

  342. protected abstract float getGestureChildUpOffset(int dy);

  343. /**

  344. * 月日历根据手势向下移动的距离

  345. *

  346. * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动

  347. * @return 根据不同日历的交互,计算不同的滑动值

  348. */

  349. protected abstract float getGestureMonthDownOffset(int dy);

  350. /**

  351. * Child根据手势向下移动的距离

  352. *

  353. * @param dy 当前滑动的距离 dy>0向上滑动,dy<0向下滑动

  354. * @return 根据不同日历的交互,计算不同的滑动值

  355. */

  356. protected abstract float getGestureChildDownOffset(int dy);

  357. /**

  358. * 跳转日期

  359. *

  360. * @param formatDate

  361. */

  362. public void jumpDate( String formatDate) {

  363. if (STATE == Attrs.MONTH) {

  364. monthCalendar.jumpDate(formatDate);

  365. } else {

  366. weekCalendar.jumpDate(formatDate);

  367. }

  368. }

  369. /**

  370. * 日历初始化的日期

  371. * @param formatDate

  372. */

  373. public void setInitializeDate(String formatDate) {

  374. monthCalendar.setInitializeDate(formatDate);

  375. weekCalendar.setInitializeDate(formatDate);

  376. }

  377. /**

  378. * 回到今天

  379. */

  380. public void toToday() {

  381. if (STATE == Attrs.MONTH) {

  382. monthCalendar.toToday();

  383. } else {

  384. weekCalendar.toToday();

  385. }

  386. }

  387. /**

  388. * 自动滑动到周视图

  389. */

  390. public void toWeek() {

  391. if (STATE == Attrs.MONTH) {

  392. onAutoToWeekState();

  393. }

  394. }

  395. /**

  396. * 自动滑动到月视图

  397. */

  398. public void toMonth() {

  399. if (STATE == Attrs.WEEK) {

  400. onAutoToMonthState();

  401. }

  402. }

  403. /**

  404. * 设置小圆点

  405. *

  406. * @param pointList

  407. */

  408. public void setPointList(List<String> pointList) {

  409. weekCalendar.setPointList(pointList);

  410. monthCalendar.setPointList(pointList);

  411. }

  412. /**

  413. * 获取当前日历的状态

  414. * Attrs.MONTH==月视图 Attrs.WEEK==周视图

  415. *

  416. * @return

  417. */

  418. public int getState() {

  419. return STATE;

  420. }

  421. /**

  422. * 下一页

  423. */

  424. public void toNextPager() {

  425. if (STATE == Attrs.MONTH) {

  426. monthCalendar.toNextPager();

  427. } else {

  428. weekCalendar.toNextPager();

  429. }

  430. }

  431. /**

  432. * 上一页

  433. */

  434. public void toLastPager() {

  435. if (STATE == Attrs.MONTH) {

  436. monthCalendar.toLastPager();

  437. } else {

  438. weekCalendar.toLastPager();

  439. }

  440. }

  441. /**

  442. * 设置日期区间

  443. *

  444. * @param startFormatDate

  445. * @param endFormatDate

  446. */

  447. public void setDateInterval(String startFormatDate, String endFormatDate) {

  448. monthCalendar.setDateInterval(startFormatDate, endFormatDate);

  449. weekCalendar.setDateInterval(startFormatDate, endFormatDate);

  450. }

  451. /**

  452. * 日期、状态回调

  453. *

  454. * @param onCalendarChangedListener

  455. */

  456. public void setOnCalendarChangedListener(OnCalendarChangedListener onCalendarChangedListener) {

  457. this.onCalendarChangedListener = onCalendarChangedListener;

  458. }

  459. /**

  460. * 点击不可用的日期回调

  461. *

  462. * @param onClickDisableDateListener

  463. */

  464. public void setOnClickDisableDateListener(OnClickDisableDateListener onClickDisableDateListener) {

  465. monthCalendar.setOnClickDisableDateListener(onClickDisableDateListener);

  466. weekCalendar.setOnClickDisableDateListener(onClickDisableDateListener);

  467. }

  468. }

 

本文首发于DF创客社区,作者:不脱发的程序猿 【脑洞大赛】基于NB-IoT智慧路灯监控系统(项目简介) 项目背景 每当夜幕降临,城市中各种各样、色彩缤纷的路灯亮起,为城市披上了一层绚丽的外衣。但在这绚丽的外表下则隐藏着巨大缺点: 1)能源浪费:由于城市的夜晚进入后半夜后,人们已经开始休息,街上人流量开始减少,有些地段在特殊时段根本不需要过多的路灯照明,导致能源浪费,增加了不必要的成本; 2)维护困难:由于使用人工巡检,需要大量人力,而路灯数量庞大,路灯实时状态不能及时获取,导致路灯故障维护、排查效率极低。 需求分析 路灯管理平台的建设,是智慧城市的一个重要组成部分,不仅能够实现城市及市政服务能力提升,也是智慧城市的一个重要入口,可促进“智慧市政”和“智慧城市”在城市照明业务方面的落地。以往在路灯管理上存在着很多问题,例如路灯开关、巡查、维护基本靠人力,重点地段晚上需要派人巡视,以保证设备的齐备率;路灯发生故障时,检修人员无法确定路灯精确位置;管理人员对路灯无法进行分时段控制、无法监控路灯整体状态;路灯保持常亮状态效率低、不节能。针对以上问题,本系统设计的智慧路灯监控系统结构图如下所示: 具体功能如下: 1)路灯节点支持自定义控制方式,可支持自定义时间控制策略和多样化控制(两侧路灯全亮、全关、隔杆高亮等)两种方式。 2)根据所在环境光照强度,自动调节路灯亮度,低功耗节能减排。 3)断电保护,电压电流超过安全阈值,路灯自动断电。 4)路灯故障自动报警,GPS精确定位,可从手机APP、微信小程序、PC端和Web平台可视化监控路灯信息,随时可调取任何一处路灯信息。 5)实时采集路灯节点工作状态、电压、电流、功率、功率因数、耗电量、产生二氧化碳、频率、环境光照度和路灯状态数据,实现统计分析和历史查询。 6)根据路灯节点历史数据,使用机器学习算法,分析路灯使用状况,构建精准城市路灯画像。 总之,基于NB-IoT技术的城市道路智慧路灯监控系统有着广阔的前景和宽广的需求。 功能设计 基于NB-IoT技术的城市道路智慧路灯监控系统,在每个照明节点上安装一个集成了NB-IoT模组的单灯控制器,单灯控制器再经运营商的网络,与路灯控制平台实现双向通信,路灯控制平台直接对每个灯进行控制,包括开关灯控制、光照检测、自动调节明暗、电耗分析等操作。智慧路灯实物图如下所示: 与传统“两跳”方案不同,基于NB-IoT技术的解决方案不需要网关,每个NB-IoT路灯控制器直接接入运营商的NB-IoT网络,即可与控制平台通信,如下图所示。 基于NB-IoT技术的城市道路智能路灯监控系统包括感知层、网络层和应用层。 感知层由单独的路灯控制模块和NB-IoT终端构成。道路上的每个路灯都安装1个路灯控制模块,路灯控制模块管理路灯的开关、负责数据信息的采集和监控路灯的运行状态,它通过NB-IoT网络与NB-IoT终端进行无线通信;NB-IoT终端将路灯控制模块采集的数据信息上传到NB-IoT基站,将应用层中的手机或监控中心的管理命令下达到路灯控制模块,对感知层的路灯进行管理和监控,使用AI技术赋能,构建精准城市路灯使用状况画像分析。网路层由NB-IoT基站和Internet网络构成。Internet网络主要应用4G的LTE平台,将感知层的数据信息实时地传送到应用层,同时将应用层的控制命令传送到感知层。智能路灯监控系统网络架构如下图所示。 本系统设计方案具有以下优势: 1)对路灯的控制上,采用分时段的3种控制策略,可以实现分时间段控制道路两侧路灯全亮模式、自动调整模式(根据环境光照强弱或电压电流阈值)、终端联控模式,在满足照度需求的情况下,实现对电能的节省。 2)通信方式所采用的是中国电信的NB-IoT网络,拓扑简单、部署成本低,NB-IoT采用DRX模式,实现终端的实时在线,这种通信方式更适合静止的和低移动性且需要下发指令的场景。 3)利用GPS地理信息管理系统,可以在手机APP、微信小程序PC应用和Web平台界面上直观定位每个路灯的位置,便于维修人员确定故障路灯地址、及时维修。 4)高精度数据采集与通信技术,可采集路灯节点工作状态、电压、电流、功率、功率因数、耗电量、产生二氧化碳、频率、环境光照度和路灯状态数据,数据精度达到小数点后两位。 5)智能化设计,使用机器学习算法,实现路灯使用状态的分析和评估。 本项目目前已经完全实现,演示视频如下所示: 基于NB-IoT智慧路灯监控系统(设备选型)基于NB-IoT智慧路灯监管系统在感知层可实现实时采集路灯节点的工作状态(亮灭状态)、电压、电流、功率、功率因数、耗电量、产生二氧化碳、频率、环境光照度、路灯亮度、路灯故障地理位置11种传感数据信息。 路灯控制终端节点主要由主控制器、NB-IoT无线通信模块、GPS模块、光强检测
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值