短学期-tinyos-辣鸡单机电脑-数据web展示

泻药。

 

 

欢迎指正。

 

方案设计

正常来说,整体架构应如此:

短学期-tinyos-辣鸡单机电脑-数据web展示_第1张图片

箭头表示数据流,数据来自传感器,热数据,放内存数据库,随取随用,过期丢掉。

 

整个系统能够在自己电脑上跑是最好的,那个机房的单机电脑真的不想碰。

 

 第一步要实现的是,PrintfClient能够将数据写入到数据库。这个PrintfClient.java文件在这(cygwin下):

\opt\tinyos-2.x\support\sdk\java\net\tinyos\tools

java工程根目录在:\opt\tinyos-2.x\support\sdk\java\

下面的MakeFile也都齐全,本来以为把java目录这个拷走,回去在linux下编译一下就可以了。

失败了。发现 java 里面有很多jni,需要调用底层C++去实现一些功能,那么就需要把sdk目录(包括C,cpp,java,python) 都拷来,尝试编译。

跑到linux编译,报错了。很奇怪,看了下make版本,4.1,再看机房make版本3.81.折腾半天,下载make3.81源码,编译,编译出来的结果make(3.81版)文件再去编译这个java源码,还是报错了。说什么未找到ncc命令。g++,gcc也都有,我估计这个ncc是nesc的compiler,ncc,也不知是不是,管他嘞。  那么应该是要把tinyos的那些环境都配上去,看了下,这个要装,那个要装,算了。  找一个简化的方案,第二天去那个辣鸡电脑上强撸。

 

简化方案

浏览器限制,javascript不能直接拉到这些想要的,一定要通过服务器。

那台电脑,连个正常的ide都没有,装个chrome要找半天兼容的安装包,都叫windows server了,想用自带的 IIS 搞个服务,却一直说资源文件占用, 想装个sublime上去,一直没装上,后放弃。 ......

那么再折腾个数据库就太费心思了,直接存个文件得了。诺这样。

短学期-tinyos-辣鸡单机电脑-数据web展示_第2张图片

这样那个PrintfClient也好改。

这个替代数据库的文件,用二进制。因为用json是最好的,但是java那边搞那些支持json的包又麻烦,如果文本文件还不如就二进制存,前端也好读取。大小限制,因为一方面过期数据没用了,另一方面,文件越来越大,读写会越来越慢,不行。

 

方案实施

 

分析PrintfClient

因为之前打印板子上发来的数据都是这样:

java net.tinyos.tools.PrintfClient -comm serial@COM4:telosb

然后数据会被打印到控制台下。

这里注意,重定向输出到文件,比如:

java net.tinyos.tools.PrintfClient -comm serial@COM4:telosb > data.txt

在这个情况下不适用,因为PrintfClient这个进程会一直开着,不断地打印数据(只要有收到),所以data.txt会持续占用,其他进程无法访问,只有进程结束才能访问到data.txt的数据。无法实现实时性。

所以要在java打印数据那里将它存下。

运气好,这个功能就写在PrintfClient里,没跑到别的文件里去。分析PrintfClient.java。

package net.tinyos.tools;

import java.io.IOException;

import net.tinyos.message.*;
import net.tinyos.tools.*;
import net.tinyos.packet.*;
import net.tinyos.util.*;

public class PrintfClient implements MessageListener {

  private MoteIF moteIF;
  
  public PrintfClient(MoteIF moteIF) {
    this.moteIF = moteIF;
    this.moteIF.registerListener(new PrintfMsg(), this);//注释2
  }

  public void messageReceived(int to, Message message) {//注释3
    PrintfMsg msg = (PrintfMsg)message;
    for(int i=0; i]");
  }
  
  public static void main(String[] args) throws Exception {
    String source = null;
    if (args.length == 2) {
      if (!args[0].equals("-comm")) {
	       usage();
	       System.exit(1);
      }
      source = args[1];
    }
    
    PhoenixSource phoenix;
    if (source == null) {
      phoenix = BuildSource.makePhoenix(PrintStreamMessenger.err);
    }
    else {
      phoenix = BuildSource.makePhoenix(source, PrintStreamMessenger.err);
    }
    System.out.print(phoenix);
    MoteIF mif = new MoteIF(phoenix);
    PrintfClient client = new PrintfClient(mif);//注释1
  }
}

main函数获取-comm后的参数。

   注释1,main函数最后,创建监听实例。

  注释2,注册监听器,第二个参数this,传的是接口MessageListener ,监听器内部通过MessageListener.messageReceived可以将事件通知到这个类。

   注释3是接口MessageListener的函数,回调函数。在收到数据的事件发生时,这个函数会被调用。

回调函数中注释4将数据一个字节一个字节以字符方式打印到控制台。

数据可以在这里获取,由于如100由三个字符组成,所以要拼接了,最后处理。顺便测试(测试是不是真的在这里打印)



测试,验证猜想

public void messageReceived(int to, Message message) {
  	String line = "";
    PrintfMsg msg = (PrintfMsg)message;
    for(int i=0; i

保存,接下来要编译,这里也卡了一下。

 在java文件夹下面这一级目录:

短学期-tinyos-辣鸡单机电脑-数据web展示_第3张图片

键入 make 。

等待十几秒,编译完成,没有显示Error就可以。

在PrintfClient.java旁边生成了PrintfClient.class。注意PrintfClient的package是net.tinyos.tools,所以会有其他依赖,运行时要跑到这里

短学期-tinyos-辣鸡单机电脑-数据web展示_第4张图片

对,还是这里,运行   java net.tinyos.tools.PrintfClient -comm serial@COM4:telosb

COM4还是COM5看一下。

不出意外的话,失败了。还是之前的程序,控制台下没有任何 "test__" 的字眼儿。

就像程序没有改一样。

2019年7月2日早9点多,我大惊失色。第一反应想到的是,打印的语句不在那,那个回调根本没有被执行。

那会在哪?翻出所有的java文件,grep 全局搜索System.out, System.err 等关键字样,在可疑处打log。

折腾个十几分钟,仍然没有任的收获,就不知道在哪里打印的数据。

有点慌,不行,心态调平,冷静一下,出去走走。

走到学校边缘,西溪医院前面,一对父子在那钓鱼,好不悠闲。

 

想到,之前在别的路径下,也可以就java net.tinyos .........    来调用这个java程序,那说明这个class被放到java的一个全局的什么地方了,可能是classpath下,所以全局能够访问,并且优先级高于当前目录下的这个class。这个不知道是不是叫双亲委派机制,没仔细研究,有待查证。

解决方法简单,新建一个java文件就行了,在PrintfClient.java旁边新建一个文件,叫啥都行,这里用PrintfClient2.java,然后再把之前修改过的PrintfClient内容放里头。注意public class 后面和构造函数类名别忘记换,main函数最底下还有。唱跳rap篮球+f 一下搜索保险起见。

然后再编译,无错误。

在java文件夹下那一级运行 java net.tinyos.tools.PrintfClient2 -comm serial@COM4:telosb

运行新的PrintfClient2,发现打印 test__ + 原始char的拼接。 测试成功

说明注释3的回调的数据确实是需要显示的数据。

 

PrintfClient中将数据写入文件

这里随便弄,没啥坑。

就是有一点,在cygwin里面跑java,创建文件的路径要以windows的路径格式来书写,例如这里:

private final String FILE_PATH1 = "F:\\zhou\\nginx-1.14.0\\html\\data1.bin";

文件名随意,开始错误写法 :

 

private final String FILE_PATH = "/tmp/data.bin";

一直就是FileNotFoundException,虽然环境是仿linux,但java里文件存储还得按windows来。

我觉得可能是cygwin里面的java虚拟机还是windows平台的吧,哦不不知道,近日晚上脑子很

 

这里的设计模式是这样的。文件50个int,50*4个字节。由于一开始可能刚启动没那么多数据,所以有多少是多少。

写入文件用:

OutputStream os = null;

os = new FileOutputStream(filepath);

loadDataToBuffer(res);
os.write(dataBuffer, 0, bufferContSize);
os.flush();

os.close();

那些try-catch没放进去,太乱。

保存文件长这样:

短学期-tinyos-辣鸡单机电脑-数据web展示_第5张图片

一个萝卜一个坑,4个字节表示一个数据。这里是小端字节序,因为javascript默认小端字节序。

第一个数据是: 00 00 00 0a  也就是 十进制的10.

会发现好浪费,一个byte就够存了,其实为扩展性,2个byte作一个整数就是Uint16Array就够了嘛,会节约些。

前端javascript读取就

var oReq2 = new XMLHttpRequest();
oReq2.open("GET", "/data2.bin", true);
oReq2.responseType = "arraybuffer";
oReq2.onload = function (oEvent) {//回调
	var arrayBuffer2 = oReq2.response; 
	if (arrayBuffer2) {
    	var byteArray2 = new Uint32Array(arrayBuffer2);
		for(j=0;j

这种方式就可以省去动态服务器,tomcat这种,直接静态服务器就可以完成数据读取。

javascript不管文件多大,把数据都拿到放在图表里展示。也不用做多余的数据转换。就比较好。

json的话可读性更高,但问题是那个辣鸡单机win server2003电脑可不能折腾。

 

当然,文件保存成文本txt也行,就是要手动split,解析格式,麻烦一些。

因为这个文本不是数据库,没有同步机制,两个进程同时访问,有概率,一个进程打开,另一个进程就无法访问,所以我这里,连着3次打开文件,失败就sleep 10毫秒,再尝试打开。测试过来没什么问题。

boolean breakFlag = false;

for (int __i = 0; __i < 3 && !breakFlag; ++__i) {
	try {
		os = new FileOutputStream(filepath);
        //.....//
		breakFlag = true;//写入成功了
	} catch (Exception e) {
		e.printStackTrace();
		breakFlag = false;//失败
	} finally {
        //在这里关闭os
	}
	if (!breakFlag) {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

数据结构这样设计的:


private String filepath;
private final int MAX_DATA_NUM = 50;
private int ptr = 0;
private int[] dataPool = new int[MAX_DATA_NUM];
private byte[] dataBuffer = new byte[MAX_DATA_NUM * 4];// 1 int -- 4 byte
private int bufferContSize = 0;

dataPool是数据存放的地方,ptr指向右边空闲位置下标,容量满了,就把数据左移,新的数据放在右边。

短学期-tinyos-辣鸡单机电脑-数据web展示_第6张图片

dataBuffer是dataPool的byte型,bufferContSize表示dataBuffer里头有多少数据需要存。

容量控制的机制:(以下部分写完才发现,用循环数组不仅效率高,编码也更方便,不改了。。。)

public void loadDataToBuffer(List res) {//插入数据在res中
		int len = res.size();// 人为保证 len < MAX_DATA_NUM
		int start = ptr;
		if (ptr + len > MAX_DATA_NUM) {//超过容量,需要左移腾出地方
			// shift <--- offset
			int offset = ptr + len - MAX_DATA_NUM;//左移offset位
			if (offset >= ptr) {// ptr左边全部丢弃
				start = 0;//start表示数据插入位置下标
			} else {//ptr左侧左移offset位
				start = ptr - offset;//ptr -> start
				for (int i = 0; i < start; ++i) {
					dataPool[i] = dataPool[i + offset];
				}
			}
		}
		ptr = start;
		for (int i = 0; i < len && ptr < MAX_DATA_NUM; ++i, ++ptr) {//写入新的数据
			dataPool[ptr] = res.get(i);
		}
		bufferContSize = 0;
		for (int i = 0; i < ptr; ++i) {//写入缓冲区
			int __d = dataPool[i];
			for (int j = 0; j < 4; ++j) {//此处小端字节序,原数据低字节的先被写入
				dataBuffer[bufferContSize++] = (byte) (__d & 0xff);//低8位
				__d >>= 8;//右移8位
			}
		}
	}

传入的是List是为了扩展性,免得到时候需要写很多数据,又得在机房烂电脑上改。

以上非必须,只要保证文件不要太大,读写太慢就行。

 

 

把所有功能封装到类中,就更好了。我封装了。

这样就可以一个nodeid绑定一个数据缓冲处理类了,互相不冲突。

 

对了,还有那个res数据,res数据就是从传感器收到的字串处理得来的。

我这里是每个int按空格分割。

public List parseLine(String line) {//上面回调函数中的char拼接成line
	System.out.println(line);
	List res = new ArrayList();
	String[] contents = line.split(" ");
	for (String cont : contents) {
		if (cont.length() > 0) {
			try {
				int value = Integer.parseInt(cont);
				res.add(value);
			} catch (Exception e) {
				// do nothing
			}

		}
	}
	return res;
}

传感器只发int型数据,别的不发,就免得多余的处理,空格分割得到List res,存入缓冲区,再存文件。

PrintfClient就是这些。

代码结构。


package net.tinyos.tools;

/*
  import ......
*/
  
class DataHelper{
	private String filepath;
	private final int MAX_DATA_NUM = 50;
	
	private int ptr = 0;
	private int[] dataPool = new int[MAX_DATA_NUM];
	private byte[] dataBuffer = new byte[MAX_DATA_NUM * 4];// 1 int -- 4 byte
	private int bufferContSize = 0;
	
	public DataHelper(String filepath){
		this.filepath = filepath;
	}
	
	public void saveToFile(List res);
	
	public void test_showDataPool() {
		for (int i = 0; i < ptr; ++i) {
			System.out.print(dataPool[i] + " ");
		}
		System.out.println();
	}

	public void loadDataToBuffer(List res);
	
}

public class PrintfClient2 implements MessageListener {

  private MoteIF moteIF;
  
  private final String FILE_PATH1 = "F:\\zhou\\nginx-1.14.0\\html\\data1.bin";
  DataHelper dataHelper1 = new DataHelper(FILE_PATH1);
 
  public PrintfClient2(MoteIF moteIF);

  public void messageReceived(int to, Message message) {
    System.out.println("messageReceived");
  	String line = "";
    PrintfMsg msg = (PrintfMsg)message;
    for(int i=0; i ll = parseLine(line);
	dataHelper1.saveToFile(tmp);
	dataHelper1.test_showDataPool();
  }
  public List parseLine(String line);
  private static void usage();
  public static void main(String[] args);
}

 

服务器搭建

模拟数据库的文件有了,但是javascript不能去静态的访问本地资源,要通过服务器的方式。

tomcat动态的,nginx静态服务器,python2的SimpleHttpServer, python3的http.server 都阔以。

我还是用的nginx,因为这个最轻量,才1MB多,而且静态文件处理性能极高。

nginx的conf目录下有配置文件nginx.conf。这里面,啥也不改,就行了,只要端口不冲突。

还有文件缓存默认这样就可以了。收到条件请求,文件如没更新,就返回304,更新了,就200,不会有错。

所以配置文件就端口设下就好。

server {
        listen       30103;

......
}

(默认80很容易冲突。)

双击一下nginx.exe,服务就跑起来了。根目录在html文件夹下(没有修改配置那个部分的前提下)

浏览器 http://localhost:30103/index.html 就是html文件夹里面的index.html默认网页。(如果是IE浏览器访问一定要加http://,开始没加,卡了一下)

之后写的前端网页都放这个目录里面。模拟数据库的文件也放这里面,因为要能够被访问。

目前的结构。

短学期-tinyos-辣鸡单机电脑-数据web展示_第7张图片

您瞧瞧,这文件时间,2010年,这辣鸡电脑连时间都不准的。

2018年的index.html和50x.html是nginx下自带文件,没用的。

js框架ecahrts 1985年什么鬼。

test.html中javascript动态地拿  "./data1.bin" 的数据,向服务器拿就不会有问题。

服务器搭建完成。

不爽的是,电脑还原,每次去都要重新配。

 

前端渲染数据

拿数据,开始用的jquery拿,结果好像网上很多说jquery拿二进制数据有问题,我测了下也是有问题。后面就改用XMLHttpRequest了。

最好装个chrome,那里的只有IE。

拿到二进制数据XMLHttpRequest前面有写,如果文本文件那jquery方便很多。

数据渲染框架多了去,套套框架比较方便的。

setInterval 定时去拿数据,因为本地localhost,所以不用想时延什么的,setInterval里面回调拿到数据,更新视图,实现实时显示数据。

setInterval(()=>{
    //拿数据,处理,更新视图
}, 500);

 

 

 

好了,时候不早了。我们下期再见。

短学期-tinyos-辣鸡单机电脑-数据web展示_第8张图片

你可能感兴趣的:(短学期-tinyos-辣鸡单机电脑-数据web展示)