用NodeJs来控制硬件(基于Raspberry Pi)(三)

用NodeJs来控制硬件(基于Raspberry Pi)(三)

上次说到为什么选择通过扩展NodeJs来实现硬件控制,接下来就看看如何来扩展:

NodeJs的扩展是非常容易的,像几乎所有的教程一下,我们先来个HelloWorld:

// helloworld.cc

#include <node.h> //首先引入node头文件

using namespace v8; //使用V8和,node命名空间 using namespace node;

/**

  • 这个就是我们要扩展到NodeJs的c函数了,函数的原型一定得是
  • Handle<Value> function_name(const Arguments& args);
  • 里面就可以使用C++写各种底层操作了 */ Handle<Value> sayHelloWorld(const Arguments& args){ HandleScope scope; printf(“Hello World!\n”); //打印一个“Hello World!” return scope.Close(Undefined()); //返回undefined }

/**

  • 初始化函数,将需要导出的方法绑定到js对象上
  • 类似 export.XXX = function(){}; */ void init(Handle<Object> target) { NODE_SET_METHOD(target, “say”, sayHelloWorld); }

NODE_MODULE(hello_world, init); //Node require的时候会执行的函数,整个模块的入口

有了C文件,怎么编译呢?NodeJs提供了node-gyp作为模块的编译工具,使用起来也非常简单,所需要的仅仅只有一个Json格式的binding.gyp文件**(里面的注释只是为了说明参数含义,但是会破坏Json结构,实际使用的时候需要去掉)**:

//binding.gyp
{
    "targets": [{
        "target_name": "helloworld", //模块名称
        "sources": [
			"helloworld.cc"  //源代码列表
		]
	}]
}

好了,我们所需要的就是这些了。接下来就是编译了:

node-gyp configure #生成Makefile等依赖文件
node-gyp build #编译模块

如果你够幸运的话,应该能在当前目录下的 build/Release/helloworld.node 找到生成的模块。

写个简单的脚本测试一下:

var helloworld = require("./build/Release/helloworld.node");
	helloworld.say();

如果没有错的话,应该能看到控制台输出“Hello World!

OK,下面是正式的文件:

// wiringpi.cc
#define BUILDING_NODE_EXTENSION

#include <v8.h> #include <node.h> #include <node_buffer.h> #include <wiringPi.h> #include <wiringShift.h>

using namespace v8; using namespace node;

#define WIRING_FUNC(NAME) wiring##NAME #define WIRING_DEFINE_CONSTANT(NAME, VALUE) (target)->Set(
v8::String::NewSymbol(NAME),
v8::Integer::New(VALUE),
static_cast<v8::PropertyAttribute>(v8::ReadOnly|v8::DontDelete)
); #define WIRING_BIND_METHOD(NAME) NODE_SET_METHOD(target, #NAME, WIRING_FUNC(NAME)); #define WIRING_WRAP_FUNC(NAME) static Handle<Value> WIRING_FUNC(NAME) (const Arguments& args) #define WIRING_RETURN(RET) return scope.Close(RET); #define WIRING_RETURN_UNDEFINED WIRING_RETURN(Undefined()) #define WIRING_ARGCHK_LEN(N) if (args.Length() < N) {
ThrowException(Exception::TypeError(String::New(“Need “#N” arguments.”)));
WIRING_RETURN_UNDEFINED
} #define WIRING_ARGCHK_INT(N,BIT) if (!args[N]->IsNumber() || !args[N]->IsInt##BIT()) {
ThrowException(Exception::TypeError(String::Concat(String::New(“Argument “#N” type error. need int”#BIT", get “), args[N]->ToString())));
WIRING_RETURN_UNDEFINED
} #define WIRING_ARGCHK_UINT(N,BIT) if (!args[N]->IsNumber() || !args[N]->IsUint##BIT()) {
ThrowException(Exception::TypeError(String::Concat(String::New(“Argument “#N” type error. need uint”#BIT”, get "), args[N]->ToString())));
WIRING_RETURN_UNDEFINED
} #define WIRING_NUMBER_FUNC_VOID(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_RETURN(Number::New(NAME()))
} #define WIRING_NUMBER_FUNC_INT(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_ARGCHK_LEN(1)
WIRING_ARGCHK_INT(0, 32)
WIRING_RETURN(Number::New(NAME(args[0]->Int32Value())))
} #define WIRING_UNDEFINED_FUNC_INT(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_ARGCHK_LEN(1)
WIRING_ARGCHK_INT(0, 32)
NAME(args[0]->Int32Value());
WIRING_RETURN_UNDEFINED
} #define WIRING_UNDEFINED_FUNC_UINT(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_ARGCHK_LEN(1)
WIRING_ARGCHK_UINT(0, 32)
NAME(args[0]->Uint32Value());
WIRING_RETURN_UNDEFINED
} #define WIRING_UNDEFINED_FUNC_INT_INT(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_ARGCHK_LEN(2)
WIRING_ARGCHK_INT(0, 32)
WIRING_ARGCHK_INT(1, 32)
NAME(args[0]->Int32Value(), args[1]->Int32Value());
WIRING_RETURN_UNDEFINED
} #define WIRING_NUMBER_FUNC_INT_INT(NAME) WIRING_WRAP_FUNC(NAME) {
HandleScope scope;
WIRING_ARGCHK_LEN(2)
WIRING_ARGCHK_INT(0, 32)
WIRING_ARGCHK_INT(1, 32)
WIRING_RETURN(Number::New(NAME(args[0]->Int32Value(), args[1]->Int32Value())))
}

// wiringPi.h: // Basic wiringPi functions

//extern int wiringPiSetup (void) ; WIRING_NUMBER_FUNC_VOID(wiringPiSetup) //extern int wiringPiSetupSys (void) ; WIRING_NUMBER_FUNC_VOID(wiringPiSetupSys) //extern int wiringPiSetupGpio (void) ; WIRING_NUMBER_FUNC_VOID(wiringPiSetupGpio) //extern int wiringPiSetupPiFace (void) ; WIRING_NUMBER_FUNC_VOID(wiringPiSetupPiFace)

//extern int wiringPiSetupPiFaceForGpioProg (void) ; // Don’t use this - for gpio program only

//extern void (*pinMode) (int pin, int mode) ; WIRING_UNDEFINED_FUNC_INT_INT(pinMode) //extern void (*pullUpDnControl) (int pin, int pud) ; WIRING_UNDEFINED_FUNC_INT_INT(pullUpDnControl) //extern void (*digitalWrite) (int pin, int value) ; WIRING_UNDEFINED_FUNC_INT_INT(digitalWrite) //extern void (*pwmWrite) (int pin, int value) ; WIRING_UNDEFINED_FUNC_INT_INT(pwmWrite) //extern void (*setPadDrive) (int group, int value) ; WIRING_UNDEFINED_FUNC_INT_INT(setPadDrive) //extern int (*digitalRead) (int pin) ; WIRING_NUMBER_FUNC_INT(digitalRead) //extern void (*delayMicroseconds) (unsigned int howLong) ; WIRING_UNDEFINED_FUNC_UINT(delayMicroseconds) //extern void (*pwmSetMode) (int mode) ; WIRING_UNDEFINED_FUNC_INT(pwmSetMode) //extern void (*pwmSetRange) (unsigned int range) ; WIRING_UNDEFINED_FUNC_UINT(pwmSetRange) // //// Interrupts // //extern int (*waitForInterrupt) (int pin, int mS) ; WIRING_UNDEFINED_FUNC_INT_INT(waitForInterrupt) // //// Threads // //#define PI_THREAD(X) void *X (void *dummy) // //extern int piThreadCreate (void *(*fn)(void *)) ; //extern void piLock (int key) ; WIRING_UNDEFINED_FUNC_INT(piLock) //extern void piUnlock (int key) ; WIRING_UNDEFINED_FUNC_INT(piUnlock) // //// Schedulling priority // //extern int piHiPri (int pri) ; WIRING_NUMBER_FUNC_INT(piHiPri) // // //// Extras from arduino land // //extern void delay (unsigned int howLong) ; WIRING_UNDEFINED_FUNC_UINT(delay) //extern unsigned int millis (void) ; WIRING_NUMBER_FUNC_VOID(millis)

// wiringShift.h:

//extern uint8_t shiftIn (uint8_t dPin, uint8_t cPin, uint8_t order) ; WIRING_WRAP_FUNC(shiftIn) { HandleScope scope; WIRING_ARGCHK_LEN(3) WIRING_ARGCHK_UINT(0, 32) WIRING_ARGCHK_UINT(1, 32) WIRING_ARGCHK_UINT(2, 32) WIRING_RETURN(Number::New(shiftIn(args[0]->Uint32Value(), args[1]->Uint32Value(), args[2]->Uint32Value()))) }

//extern void shiftOut (uint8_t dPin, uint8_t cPin, uint8_t order, uint8_t val) ; WIRING_WRAP_FUNC(shiftOut) { HandleScope scope; int dPin, cPin, order;

Local&lt;Object&gt; bufferObj;
char *buffer = NULL;
size_t length = 0, index = 0;

WIRING_ARGCHK_LEN(4)
WIRING_ARGCHK_UINT(0, 32)
WIRING_ARGCHK_UINT(1, 32)
WIRING_ARGCHK_UINT(2, 32)

dPin = args[0]-&gt;Uint32Value();
cPin = args[1]-&gt;Uint32Value();
order = args[2]-&gt;Uint32Value();

if (Buffer::HasInstance(args[3])) {
	bufferObj = args[3]-&gt;ToObject();
	buffer = Buffer::Data(bufferObj);
	length = Buffer::Length(bufferObj);
	for(index = 0; index &lt; length; index++){
		shiftOut(dPin, cPin, order, buffer[index]);
	}
}else{
	WIRING_ARGCHK_UINT(3, 32)
	shiftOut(dPin, cPin, order, args[3]-&gt;Uint32Value());
}
WIRING_RETURN_UNDEFINED

}

extern “C” {

void init(Handle<Object> target) { WIRING_BIND_METHOD(wiringPiSetup) WIRING_BIND_METHOD(wiringPiSetupSys) WIRING_BIND_METHOD(wiringPiSetupGpio) WIRING_BIND_METHOD(wiringPiSetupPiFace) WIRING_BIND_METHOD(pinMode) WIRING_BIND_METHOD(pullUpDnControl) WIRING_BIND_METHOD(digitalWrite) WIRING_BIND_METHOD(pwmWrite) WIRING_BIND_METHOD(setPadDrive) WIRING_BIND_METHOD(digitalRead) WIRING_BIND_METHOD(delayMicroseconds) WIRING_BIND_METHOD(pwmSetMode) WIRING_BIND_METHOD(pwmSetRange) WIRING_BIND_METHOD(waitForInterrupt) WIRING_BIND_METHOD(piLock) WIRING_BIND_METHOD(piUnlock) WIRING_BIND_METHOD(piHiPri) WIRING_BIND_METHOD(delay) WIRING_BIND_METHOD(millis) WIRING_BIND_METHOD(shiftIn) WIRING_BIND_METHOD(shiftOut) WIRING_DEFINE_CONSTANT(“INPUT”, INPUT) WIRING_DEFINE_CONSTANT(“OUTPUT”, OUTPUT) WIRING_DEFINE_CONSTANT(“PWM_OUTPUT”, PWM_OUTPUT) WIRING_DEFINE_CONSTANT(“LOW”, LOW) WIRING_DEFINE_CONSTANT(“HIGH”, HIGH) WIRING_DEFINE_CONSTANT(“PUD_OFF”, PUD_OFF) WIRING_DEFINE_CONSTANT(“PUD_DOWN”, PUD_DOWN) WIRING_DEFINE_CONSTANT(“PUD_UP”, PUD_UP) WIRING_DEFINE_CONSTANT(“LSBFIRST”, LSBFIRST) WIRING_DEFINE_CONSTANT(“MSBFIRST”, MSBFIRST) } NODE_MODULE(wiringpi, init);

}

以及 binding.gyp:

{
    "targets": [{
        "target_name": "_wiringpi",
        "include_dirs": ["src"],
        "sources": [
			"wiringpi.cc",
			"src/piThread.c",
			"src/wiringPiSPI.h",
			"src/lcd.h",
			"src/wiringSerial.c",
			"src/wiringPiSPI.c",
			"src/wiringShift.h",
			"src/gertboard.h",
			"src/wiringPi.c",
			"src/softPwm.h",
			"src/piHiPri.c",
			"src/wiringSerial.h",
			"src/wiringPi.h",
			"src/lcd.c",
			"src/piNes.c",
			"src/gertboard.c",
			"src/softPwm.c",
			"src/wiringPiFace.c",
			"src/wiringShift.c",
			"src/piNes.h"
		]
	}]
}

这样,就能在nodejs中调用wiringPi的函数了,接下来,我们就可以专注于nodejs的开发了~(待续)


15 回复

用NodeJs来控制硬件(基于Raspberry Pi)(三)

上次说到为什么选择通过扩展NodeJs来实现硬件控制,接下来就看看如何来扩展:

NodeJs的扩展是非常容易的。我们先来看一个简单的示例,类似于HelloWorld程序,我们将展示如何通过C++扩展来调用硬件相关的库。

HelloWorld 示例

// helloworld.cc

#include <node.h>    // 引入node头文件
using namespace v8;  // 使用V8和node命名空间

/**
 * 这个是我们要扩展到NodeJs的C函数。
 * 函数的原型必须是:
 * Handle<Value> function_name(const Arguments& args);
 */
Handle<Value> sayHelloWorld(const Arguments& args) {
    HandleScope scope;
    printf("Hello World!\n"); // 打印一个“Hello World!”
    return scope.Close(Undefined()); // 返回undefined
}

/**
 * 初始化函数,将需要导出的方法绑定到js对象上。
 * 类似于 export.XXX = function(){};
 */
void init(Handle<Object> target) {
    NODE_SET_METHOD(target, "say", sayHelloWorld);
}

NODE_MODULE(hello_world, init); // Node require的时候会执行的函数,整个模块的入口

有了C文件,我们需要如何编译呢?NodeJs提供了node-gyp作为模块的编译工具,使用起来非常简单。只需要一个Json格式的binding.gyp文件:

// binding.gyp
{
    "targets": [{
        "target_name": "helloworld", // 模块名称
        "sources": [
            "helloworld.cc"  // 源代码列表
        ]
    }]
}

接下来进行编译:

node-gyp configure # 生成Makefile等依赖文件
node-gyp build # 编译模块

如果一切顺利,你应该能在当前目录下的 build/Release/helloworld.node 找到生成的模块。我们可以写一个简单的脚本测试一下:

// test.js
var helloworld = require("./build/Release/helloworld.node");
helloworld.say();

运行这个脚本,如果没有错误的话,你应该能在控制台看到输出 “Hello World!”

控制硬件示例

接下来,我们将使用wiringPi库来扩展NodeJs,以便能够直接从NodeJs中控制GPIO引脚。

首先,我们需要编写C++代码来绑定wiringPi库的功能。这里是一个简化版的wiringPi扩展示例:

// wiringpi.cc

#define BUILDING_NODE_EXTENSION

#include <v8.h>
#include <node.h>
#include <node_buffer.h>
#include <wiringPi.h>

using namespace v8;
using namespace node;

void init(Handle<Object> target) {
    NODE_SET_METHOD(target, "setup", setup);
    NODE_SET_METHOD(target, "write", write);
    NODE_SET_METHOD(target, "read", read);
}

void setup(const FunctionCallbackInfo<Value>& args) {
    int mode = args[0]->Int32Value();
    wiringPiSetup();
    args.GetReturnValue().Set(Boolean::New(isolate, true));
}

void write(const FunctionCallbackInfo<Value>& args) {
    int pin = args[0]->Int32Value();
    int value = args[1]->Int32Value();
    digitalWrite(pin, value);
    args.GetReturnValue().Set(Boolean::New(isolate, true));
}

void read(const FunctionCallbackInfo<Value>& args) {
    int pin = args[0]->Int32Value();
    int value = digitalRead(pin);
    args.GetReturnValue().Set(Integer::New(isolate, value));
}

NODE_MODULE(wiringpi, init);

然后,创建一个binding.gyp文件:

{
    "targets": [{
        "target_name": "wiringpi",
        "sources": [
            "wiringpi.cc",
            "src/wiringPi.c",
            "src/wiringPi.h"
        ],
        "libraries": ["-lwiringPi"]
    }]
}

编译并测试:

node-gyp configure
node-gyp build

编写一个简单的NodeJs脚本来测试这些功能:

// test-wiringpi.js
var wiringpi = require('./build/Release/wiringpi');

wiringpi.setup(0);
wiringpi.write(0, 1); // 设置引脚0为高电平
console.log('Read pin 0:', wiringpi.read(0)); // 读取引脚0的状态

这样,你就可以在NodeJs中调用wiringPi的函数来控制GPIO引脚了。


怎么一个牛逼了得, Raspberry Pi 小白路过

学过C没用过C,看着这样的代码好恐怖啊。有啥C的好书推荐不?

大神,第一个helloworld的例子有错误 我从昨天下午开始做这个例子,开始是我的mac的node配置有问题,把和配置相关的错误都改了,还不行,但是悲剧的是我还的node配置有问题,就到windows系统里又配置了一次,结果,还是不行,噼里啪啦一直报错~~~ 最后,一直到今天下午,我看了nodejs.org的例子,结果很容易就成功了,再回头看,您代码里有错误: NODE_MODULE(hello_world, init); 这里的“hello_world”应该是“helloworld”

我吐个槽行么, 要不用 nodejs 开发个操作系统得了.

很酷,学习。

我认为绝对是可以的

大神,v8.h, 跟 node.h 你们是从哪里下的呢?

大神,v8.h, 跟 node.h 你们是从哪里下的呢?

你看的是哪个例子呀~

哥哥 wiringpi.cc文件的顶部 这句话不能加啊 搞了我好长时间 重复定义了

#define BUILDING_NODE_EXTENSION

在这个帖子中,我们将继续扩展Node.js以控制基于Raspberry Pi的硬件,并展示如何使用已经定义的wiringpi模块来控制GPIO引脚。

示例代码:控制GPIO引脚

首先,我们需要确保已安装node-gyp和所有必要的构建工具。接着,可以创建一个简单的Node.js脚本来控制GPIO引脚:

// 控制GPIO的Node.js脚本
var wiringpi = require('./build/Release/_wiringpi');

// 设置模式为输出
wiringpi.pinMode(0, wiringpi.OUTPUT); // GPIO 0设置为输出模式

// 向GPIO 0发送信号
wiringpi.digitalWrite(0, wiringpi.HIGH); // 输出高电平
setTimeout(() => {
    wiringpi.digitalWrite(0, wiringpi.LOW); // 输出低电平
}, 1000); // 延时1秒

console.log('GPIO 0状态:', wiringpi.digitalRead(0)); // 读取GPIO 0的状态

解释

  • wiringpi.pinMode(0, wiringpi.OUTPUT): 将GPIO 0设置为输出模式。
  • wiringpi.digitalWrite(0, wiringpi.HIGH): 向GPIO 0发送高电平信号。
  • wiringpi.digitalWrite(0, wiringpi.LOW): 向GPIO 0发送低电平信号。
  • wiringpi.digitalRead(0): 读取GPIO 0的状态并打印结果。

编译

确保已经生成并编译了_wiringpi模块:

node-gyp configure
node-gyp build

运行

保存上述JavaScript脚本并运行它:

node your_script.js

这将输出GPIO 0的状态,验证是否成功地控制了硬件。

通过这种方式,我们可以轻松地在Node.js中使用C/C++代码来控制Raspberry Pi上的硬件,利用了Node.js的强大异步编程模型与底层硬件的紧密集成。

回到顶部