javascript原生移动云编程13 - 通过蓝牙操控智能硬件

用javascript在码实云平台上,可以在云里编写原生的移动应用。而原生的移动应用有能力通过蓝牙与其他蓝牙设备通讯。由于多数智能硬件现在都是用蓝牙(主要是蓝牙4.0BLE)与手机通讯,因此,码实平台开发的移动应用,可以轻松地操控智能硬件。

javascript原生移动云编程13 - 通过蓝牙操控智能硬件_第1张图片

本应用实例的智能硬件是个智能小车,小车上的主要控制电路是最流行的Arduino,通过串行口连接着一个蓝牙模块(前面白色面包板上竖插的小板)。我们使用的蓝牙模块同时兼备串口透传和iBeacon的功能(码实平台另有教程详细介绍iBeacon应用的开发和配置),叫“ZeroBeacon”,产自国内蓝牙硬件公司“四月兄弟”(淘宝上可以找到)。为了简化教程,我们把硬件做的非常简单,用一个标准的L298N模块控制电机,左电机的I1、I2分别接Arduino的8和9端口,PWM调速EA接11端口。同理,右电机的I3、I4分别接入Arduino的6和7端口,PWM调速EB接10端口。蓝牙模块的VCC接Arduino的5V供电VCC,地线GND自然接Arduino的GND,蓝牙模块通讯口TX接Arduino的1端口RX,蓝牙的RX接Arduino的0端口TX。这就是硬件实例的全部信号接线。硬件部分的Arduino程序也写的非常简单。只用一条串口指令来操作小车的两个马达。下面是Arduino的C程序:

// L298N的电机控制模块
int pinI1=8;//定义I1接口 
int pinI2=9;//定义I2接口  
int speedpin=11;//定义EA(PWM调速)接口 
int pinI3=6;//定义I3接口 
int pinI4=7;//定义I4接口  
int speedpin1=10;//定义EB(PWM调速)接口 

String cmd = "";
#define IO_LIGHT 13

//初始化程序段
void setup()
{
  Serial.begin(9600);    // 启动串口通信,波特率为9600b/s
  
  pinMode(pinI1,OUTPUT);
  pinMode(pinI2,OUTPUT);
  pinMode(speedpin,OUTPUT);
  pinMode(pinI3,OUTPUT);
  pinMode(pinI4,OUTPUT);
  pinMode(speedpin1, OUTPUT);
  
  pinMode(IO_LIGHT, OUTPUT);

  Serial.print("***Ready");
}

//主程序段
void loop()
{
  // 检查是否有串口写入
  while (Serial.available() > 0)  {
    cmd += char(Serial.read());
    
    // 让板子的LED灯闪烁以示串口数据正在传输
    digitalWrite(IO_LIGHT, HIGH);
    delay(5);
    digitalWrite(IO_LIGHT, LOW);
  }
  
  if (cmd.length() > 0) {
    // 从串口读取字符串,解析小车控制命令
    if (cmd.startsWith("go:")) {
      // 命令格式 "go:f120,b80" 左轮向前速度120,右轮向后速度80
      String left = cmd.substring(3, 4);
      int pos = cmd.indexOf(",");
      int left_speed = cmd.substring(4, pos).toInt();
      cmd = cmd.substring(pos+1);
      String right = cmd.substring(0, 1);
      int right_speed = cmd.substring(1).toInt();
      control(left, left_speed, right, right_speed);
    }
    cmd = "";
  }  
}

void control(String left, int left_speed, String right, int right_speed)
{
  Serial.print(left); Serial.println(right);
  // speed control
  analogWrite(speedpin, left_speed);   
  analogWrite(speedpin1, right_speed);

  // forward/backward direction control  
  if (left == "f") {
    digitalWrite(pinI1, LOW);
    digitalWrite(pinI2, HIGH);
  } else {
    digitalWrite(pinI1, HIGH);
    digitalWrite(pinI2, LOW); 
  }
  
  if (right == "f") {
    digitalWrite(pinI4, LOW);
    digitalWrite(pinI3, HIGH);
  } else {
    digitalWrite(pinI4, HIGH);
    digitalWrite(pinI3, LOW); 
  }
}

在Arduino程序里,我们定义实现了小车左右轮的控制命令,命令格式为"go:f120,b80",解释为左轮向前速度120,右轮向后速度80。手机通过蓝牙向小车发命令,必须遵循这个命令格式。下面是在码实平台上开发的移动应用部分的javascript代码:

Class.create(Mash5.Widget, {
    peripheralManager: null,
    
	initialize : function (config, params) {
		var height = Ti.Platform.displayCaps.platformHeight - dipToPx(66);
		var width = Ti.Platform.displayCaps.platformWidth;
		
		var container = Ti.UI.createView({
			width : Ti.UI.FILL,
			height : Ti.UI.FILL,
			backgroundColor : '#fff'
		});
		
		this.setContentView(container);
		
		var box = Ti.UI.createView({
		    top: '20dip',
		    width: '240dip',
		    height: '320dip',
		    borderRadius: dipToPx(20),
		    backgroundColor: '#888',
		});
		container.add(box);
		
		// stop line indicator
		box.add(Ti.UI.createView({
		    top: '160dip',
		    width: '100%',
		    height: '1dip',
		    backgroundColor: '#f40',
		}));
		
		var matrix = Ti.UI.create2DMatrix();

		var left_level = Ti.UI.createSlider({
		    min: -50,
		    max: 50,
		    value: 0,
		    width: '220dip',
		    center: {x: '60dip', y: '160dip'},
		});
		left_level.transform = matrix.rotate(-90);
		box.add(left_level);
		var right_level = Ti.UI.createSlider({
		    min: -50,
		    max: 50,
		    value: 0,
		    width: '220dip',
		    center: {x: '180dip', y: '160dip'},
		});
		right_level.transform = matrix.rotate(-90);
		box.add(right_level);
		
		var left_value = Titanium.UI.createLabel({
    		color: '#fff',
    		text: 'L:0',
    		font: {fontSize: '16dip'},
    		center: {x: '60dip', y: '30dip'},
    	});
    	box.add(left_value);
		var right_value = Titanium.UI.createLabel({
    		color: '#fff',
    		text: 'R:0',
    		font: {fontSize: '16dip'},
    		center: {x: '180dip', y: '30dip'},
    	});
    	box.add(right_value);
		
		var stop_button = Ti.UI.createView({
		    top: '360dip',
		    width: '240dip',
		    height: '60dip',
		    borderRadius: dipToPx(30),
		    backgroundColor: '#f40',
		});
		stop_button.add(Titanium.UI.createLabel({
    		color: '#fff',
    		text: 'STOP',
    		font: {fontSize: '24dip'},
    	}));
    	container.add(stop_button);
    	
    	stop_button.addEventListener('click', function() {
    	    left_level.setValue(0);
    	    right_level.setValue(0);
    	    this.peripheralManager.writeValueForCharacteristic('FFF1', "go:f0,f0");
    	}.bind(this));
    	
    	var lval = 0;
    	var rval = 0;
    	left_level.addEventListener('change', function() {
    	    lval = Math.floor(left_level.getValue()) * 5;
    	    left_value.setText('L:' + lval);
    	});
    	right_level.addEventListener('change', function() {
    	    rval = Math.floor(right_level.getValue()) * 5;
    	    right_value.setText('R:' + new_rval);
    	});
    	
    	left_level.addEventListener('stop', function() {
    	    lval = Math.floor(left_level.getValue()) * 5;
    	    left_value.setText('L:' + lval);
    	    this.peripheralManager.writeValueForCharacteristic('FFF1', 
    	        "go:" + (rval > 0 ? "f" : "b") + Math.abs(rval) + 
    	        "," + (lval > 0 ? "f" : "b") + Math.abs(lval));
    	}.bind(this));
    	right_level.addEventListener('stop', function() {
    	    rval = Math.floor(right_level.getValue()) * 5;
    	    right_value.setText('R:' + rval);
    	    this.peripheralManager.writeValueForCharacteristic('FFF1', 
    	        "go:" + (rval > 0 ? "f" : "b") + Math.abs(rval) + 
    	        "," + (lval > 0 ? "f" : "b") + Math.abs(lval));
    	}.bind(this));
    	
		var statusLabel = Titanium.UI.createLabel({
    		color: '#fff',
    		text: '设备状态:未开电源',
    		font: {fontSize: '12dip'},
    		left: '20dip',
    		bottom: '10dip',
    	});
    	box.add(statusLabel);
		var statusTitle = "设备状态:"
		this.peripheralManager = new Mash5.BluetoothLE.PeripheralManager({
			autoConnect: false,
			names: ["ZeroBeacon"]
		});
		this.peripheralManager.addEventListener('discoveredPeripheral', function(peripheral) {
			statusLabel.text = statusTitle + '   发现外设';
			this.peripheralManager.connectPeripheral(peripheral);
		}.bind(this));
		this.peripheralManager.addEventListener('connectedPeripheral', function(peripheral) {
			statusLabel.text = statusTitle + '   已连接外设';
		});
		this.peripheralManager.addEventListener('digestServices', function(services) {
			this.peripheralManager.connectService('FFF0');
		}.bind(this));
		this.peripheralManager.addEventListener('digestCharacteristics', function() {
			statusLabel.text = statusTitle + '   外设控制就绪';
		});
		this.peripheralManager.addEventListener('failedToConnectPeripheral', function(peripheral) {
			statusLabel.text = statusTitle + '   连接外设失败';
		});
		this.peripheralManager.addEventListener('disconnectedPeripheral', function(peripheral) {
			statusLabel.text = statusTitle + '   已断开外设';
		});
		var page = this.getCurrentPage();
		page.addEventListener('close', function() {
			this.peripheralManager.close();
			this.peripheralManager = null;
		}.bind(this));
	}
})


程序前半部主要是用Ti.UI.createSlider做了左右轮子的滑杆控制界面,注意我们利用二维变换Ti.UI.create2DMatrix把滑杆从水平变成垂直。两个滑杆都用各自的“change”事件来显示数值变化,但给小车发指令是在“stop”的事件处理中进行,这样避免连续不停地发指令。通过蓝牙发指令是这个调用:

this.peripheralManager.writeValueForCharacteristic('FFF1', 
    	        "go:" + (rval > 0 ? "f" : "b") + Math.abs(rval) + 
    	        "," + (lval > 0 ? "f" : "b") + Math.abs(lval));

蓝牙设备的读写口是'FFF1'。然后就是我们在Arduino里定义好的命令格式。

程序的后半部负责蓝牙通讯的建立。首先要创建一个外设管理:

this.peripheralManager = new Mash5.BluetoothLE.PeripheralManager({
			autoConnect: false,
			names: ["ZeroBeacon"]
		});

这个蓝牙外设的名称是ZeroBeacon,蓝牙模块的出厂名称。可以通过蓝牙模块设置的AT指令去修改。后面是蓝牙连接过程中的各种事件处理,其中

this.peripheralManager.connectService('FFF0') 连接上蓝牙模块的接口服务。




你可能感兴趣的:(码实平台教程)