转:openwrt中luci学习笔记
原文地址:openwrt中luci学习笔记
最近在学习OpenWrt,需要在OpenWrt的WEB界面增加内容,本文将讲述修改OpenWrt的过程和其中遇到的问题。
一、WEB界面开发
LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三层架构,使用Lua脚本开发,所以开发LuCI的配置界面不需要编辑任何的Html代码,除非想自己单独去创建网页(View层),否则我们基本上只需要修改Model层就可以了。
首先我们讲述如何在web界面增加一个新的选项,如下图“System”旁边的“SZ-Loogson”选项卡。
在文件系统目录“/usr/lib/lua/luci/controller/admin”下创建loogson.lua文件,文件内容如下:
点击(此处)折叠或打开
- module("luci.controller.admin.loogson", package.seeall)
- function index()
- entry({"admin", "loogson"}, alias("admin", "loogson", "loogson"), _("SZ-Loogson"), 30).index = true
- entry({"admin", "loogson", "loogson"}, cbi("admin_loogson/loogson"), _("BoardType"), 1)
- entry({"admin", "loogson", "control"}, cbi("admin_loogson/control"), _("Control"), 2)
- end
说明:
1) lua单行注释使用“--”,类似于C语言的“//”,多行注释时,“--[[”类似C语言中的“/*”,“]]--”类似C语言中的“*/”。
2) 第1行定义了模块的入口。即“/usr/lib/lua/luci/controller/admin”所示的目录下建立一个loogson.lua文件。如果程序比较多,可能分为好几个模块,那么可以在controller下再建立一个子目录,比如controller/loogsonapp/,那么就可以写成“luci.controller.loogsonapp.loogson”。
3) 从第3行到10行即是function index函数,该函数定义了SZ-Loogson下各个选项卡。
entry表示添加一个新的模块入口,entry的定义如下,其中后两项都是可以为空:
点击(此处)折叠或打开
- entry(path, target, title=nil, order=nil)
“path”是访问的路径,路径是按字符串数组给定的,比如路径按如下方式写“{"admin", "loogson", "control"}”,那么就可以在浏览器里访问“http://192.168.1.1/cgi-bin/luci/admin/loogson/control”来访问这个脚本。其中的“admin”表示为管理员添加脚本,“loogson”即为一级菜单名,“control”为菜单项名。系统会自动在对应的菜单中生成菜单项。比如想在“System”菜单下创建一个菜单项,那么一级菜单名可以写为“system”。
“target”为调用目标,调用目标分为三种,分别是执行指定方法(Action)、访问指定页面(Views)以及调用CBI Module。
第一种可以直接调用指定的函数,比如点击菜单项就直接重启路由器等等,比如写为“call("function_name")”,然后在该lua文件下编写名为function_name的函数就可以调用了。
第二种可以访问指定的页面,比如写为“template("myapp/mymodule")”就可以调用/usr/lib/lua/luci/view/myapp/mymodule.htm文件了。
第三种主要应用在配置界面,比如写为“cbi("myapp/mymodule")”就可以调用/usr/lib/lua/luci/model/cbi/myapp/mymodule.lua文件了。
title和order是针对管理员菜单的,其中的title即是显示在网页上的内容。这里我们创建“/usr/lib/lua/luci/controller/loogson.lua”文件,定义我们的入口为“loogson”。
4) 从上面的程序可以看出,在“SZ-Loogson”选项卡下共有两个分选项,名称分别为“BoardType”和“Control”。根据cbi指示的目录,在“/usr/lib/lua/luci/model/cbi/admin_loogson”目录下有loogson.lua和control.lua两个文件,两个界面类似,我们选取"Control"界面讲述。
当点击上图中的"Control"选项时,即可进入如下界面:
此界面对应的程序在“/usr/lib/lua/luci/model/cbi/admin_loogson/control.lua”下,具体内容为:
点击(此处)折叠或打开
- require("luci.sys")
- require("luci.sys.zoneinfo")
- require("luci.tools.webadmin")
- require("luci.fs")
- require("luci.config")
- local m, s, o
- m = Map("loogson", translate("Control"), translate("This is design by Davied Huang, in order to control loogson board, such as led、beep、and adc."))
- m:chain("luci")
- s = m:section(TypedSection, "controlboard", translate("Control Board"))
- s.anonymous = true
- s.addremove = false
- s:tab("led", translate("Control LED"))
- s:tab("beep", translate("Control Beep"))
- --s:tab("adc", translate("Control Adc"))
- --
- -- LED
- --
- o = s:taboption("led", ListValue, "lednum", translate("LED NUM:"))
- o.default = 0
- o.datatype = "uinteger"
- o:value(0, translate("LED0"))
- o:value(1, translate("LED1"))
- o:value(2, translate("LED2"))
- o = s:taboption("led", ListValue, "ledstatus", translate("LED STATUS:"))
- o.default = 1 --off status
- o.datatype = "uinteger"
- o:value(0, translate("LED ON"))
- o:value(1, translate("LED OFF"))
- --
- -- BEEP
- --
- o = s:taboption("beep", ListValue, "beepstatus", translate("BEEP STATUS:"))
- o.default = 1 --off status
- o.datatype = "uinteger"
- o:value(0, translate("ON"))
- o:value(1, translate("OFF"))
- o = s:taboption("beep", Value, "beepfreq", translate("BEEP FREQ:"))
- o.datatype = "uinteger"
- local apply = luci.http.formvalue("cbi.apply")
- if apply then
- io.popen("/etc/init.d/loogson restart")
- end
- return m
说明:
1) 此处我们使用UCI(Unified Configuration Interface,统一配置接口)开发方式,第7行定义了三个局部变量。
2) 第9行引用了一个“Map”调用,该调用的定义为:
点击(此处)折叠或打开
- m = Map("配置文件文件名", "配置页面标题", "配置页面说明")
第一个参数为配置文件存储的文件名,不包含路径,比如按上述创建的话,应该写为“loogson”,配置文件的存储地址为:/etc/config。第二与第三个参数是用在来页面上显示的,如图所示。
3) 接下来需要创建与配置文件中对应的Section,Section分为两种,NamedSection和TypedSection,前者根据配置文件中的Section名,而后者根据配置文件中的Section类型,这里我们使用后者。我们设定不显示Section的名称(“s.anonymous = true”),以及不允许增加或删除Section(“s.addremove = false”)。controlboard即为1)中配置文件其中的一个配置。
4) 接下来我们定义了两个选项卡,分别为“Control LED”和“Control Beep”。如上图所示。
5) 对于LED选项卡的程序为24-35行。首先创建LED的Section中不同内容的交互(创建Option),常见的有Value(文本框)、ListValue(下拉框)、Flag(选择框)等。创建Option的过程非常简单,而且创建后系统会无需考虑读取以及写入配置文件的问题,系统都会自动处理。25行定义了Option的默认值,26行定义了它的数据类型,此处为整形。27到29定义了它的三个取值,比如如果你选“LED1”的话,实际写到配置文件中的值为1。
6) 对于使用UCI的方式,首先需要创建对应的配置文件(如果配置文件不存在的话,访问配置页面将会报错),格式即为linux配置文件的格式,文件存储在文件系统“/etc/config”目录下,对于本文即在“/etc/config/loogson”,内容如下:
点击(此处)折叠或打开
- config boardinfo
- option ipaddr1 '192.168.123.212'
- option netmask1 '255.0.0.0'
- option boardname '1'
- config controlboard
- option beepfreq '100'
- option beepstatus '0'
- option lednum '2'
- option ledstatus '0
7) 应用配置以后希望配置立即生效,51行到54行的代码就是判断是否点击了“应用”按钮,如果点击了“应用”按钮就执行默认的脚本程序。
二、OpenWrt添加模块(package)
OpenWrt是一个比较完善的嵌入式Linux开发平台,在无线路由器应用上已有100多个软件包。人们可以在其基础上增加软件包,以扩大其应用范围。
2.1、准备工作
OpenWrt在增加软件方面极其方便,按照OpenWrt的约定就可以很简单完成。加入的软件包可以是网上下载的开源软件或自行开发的软件。加入软件包需要在package目录下创建一个目录。然后创建一个Makefile与OpenWrt建立联系,Makefile需要遵循OpenWrt的约定。另外可以创建一个patchs目录保存patch文件,对下载的源代码进行修改。
由于本文所建立的模块是基于luci的,所以在OpenWrt的“package/feeds/luci”目录下建立“szloogson”目录。
2.2、模块Makefile
在“szloogson”目录下建立一个“Makefile”文件,该文件的如下所示:
点击(此处)折叠或打开
- #
- # Copyright (C) 2010-2014 Davied Huang Wich <apple_guet@126.com>
- #
- # This is free software, licensed under the GNU General Public License v2.
- # See /LICENSE for more information.
- #
- include $(TOPDIR)/rules.mk
- PKG_NAME:=luci-app-szloogson
- PKG_VERSION=1.0
- PKG_RELEASE:=1
- PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
- include $(INCLUDE_DIR)/package.mk
- define Package/luci-app-szloogson
- SECTION:=luci
- CATEGORY:=LuCI
- SUBMENU:=3. Applications
- TITLE:=shenzhou loogson for LuCI
- PKGARCH:=all
- endef
- define Package/luci-app-njitclient/description
- This package contains LuCI configuration pages for shenzhou loogson.
- endef
- define Build/Prepare
- mkdir -p $(PKG_BUILD_DIR)
- $(CP) ./src/* $(PKG_BUILD_DIR)/
- endef
- define Build/Configure
- endef
- define Build/Compile
- $(MAKE) -C $(PKG_BUILD_DIR) \
- CC="$(TARGET_CC)" \
- CROSS_COMPILE="$(TARGET_CROSS)" \
- ARCH="$(ARCH)"
- endef
- define Package/luci-app-szloogson/install
- #install shell
- $(INSTALL_DIR) $(1)/etc/init.d
- $(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson
- #install config
- $(INSTALL_DIR) $(1)/etc/config
- $(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson
- #install execute bin
- $(INSTALL_DIR) $(1)/usr/sbin
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/gsc3280_led
- #install luci
- mkdir -p $(1)/usr/lib/lua/luci/controller/admin
- $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
- $(INSTALL_DATA) ./src/luci/controller/admin/loogson.lua $(1)/usr/lib/lua/luci/controller/admin/loogson.lua
- mkdir -p $(1)/usr/lib/lua/luci/model/cbi/admin_loogson
- $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi
- $(INSTALL_DATA) ./src/luci/model/cbi/admin_loogson/* $(1)/usr/lib/lua/luci/model/cbi/admin_loogson/
- endef
- $(eval $(call BuildPackage,luci-app-szloogson)
说明:
2.2.1、第8行“$(TOPDIR)/rules.mk”
“$(TOPDIR)/rules.mk”一般在Makefile的开头,定义一些包的基本信息。
软件包的信息均以“PKG_”开头,其意思和作用如下:
PKG_NAME:软件包名称,将在menuconfig和ipkg可以看到。
PKG_VERSION:软件版本号。
PKG_RELEASE:Makefile的版本号
PKG_SOURCE:源代码的文件名。
PKG_SOURCE_URL:源代码的下载网站位置。@SF表示在sourceforge网站,@GNU表示在GNU网站,还有@GNOME、@KERNEL。获取方式可以为:git、svn、cvs、hg、bzr等。有关下载方法可参考$(INCLUDE_DIR)/download.mk和$(SCRIPT_DIR) /download.pl。由于本文使用的是自己开发的代码,所以没有此项。
PKG_MD5SUM:源代码文件的效验码。用于核对软件包是否下载正确。
PKG_CAT:源代码文件的解压方法。包括zcat, bzcat, unzip等。
PKG_BUILD_DIR:软件包编译目录。它的父目录为$(BUILD_DIR)。如果不指定,默认为$(BUILD_DIR)/$( PKG_NAME)-$( PKG_VERSION)。
还有一些有关源代码的定义如下:
PKG_SOURCE_SUBDIR
PKG_SOURCE_PROTO
PKG_SOURCE_MIRROR
PKG_MIRROR_MD5SUM
PKG_SOURCE_VERSION 2.2.2、第17行“include $(INCLUDE_DIR)/package.mk”
“include $(INCLUDE_DIR)/package.mk”一般在软件包的基本信息完成后再引入,他定义了用户态软件包的规则。
编译包分为用户态和内核模块,用户态软件包使用Package,内核模块使用KernelPackage。“$(INCLUDE_DIR)/kernel.mk”文件对于软件包为内核时不可缺少,“$(INCLUDE_DIR)/package.mk”应用在用户态。接下来讲述用户态软件包。用户程序的编译包以“Package/”开头,然后接着软件名,在Package定义中的软件名可以与软件包名不一样,而且可以多个定义。
2.2.3、第19行”define Package/luci-app-szloogson
包的名称为”luci-app-szloogson“。
接下来定义的包括:
SECTION:包的类型,预留。
CATEGORY:分类,在menuconfig的菜单下将可以找到。
SUBMENU:包在make menuconfig的位置,此处即在”LuCi/3. Applications“下。
TITLE:用于软件包的简短描述,将显示在”make menuconfig“中。
DESCRIPTION:用于软件包的详细描述,已放弃使用。如果使用DESCRIPTION将会提示“error DESCRIPTION:= is obsolete, use Package/PKG_NAME/description”。
URL:软件包的下载位置。
MAINTAINER:维护者选项。
DEPENDS:与其他软件的依赖。即如编译或安装需要其他软件时需要说明。如果存在多个依赖,则每个依赖需用空格分开。依赖前使用+号表示默认显示,即对象沒有选中时也会显示,使用@则默认为不显示,即当依赖对象选中后才显示。
2.2.4、第27行”define Package/luci-app-szloogson/description“
软件包的详细描述,取代前面提到的DESCRIPTION详细描述。此处定义的信息将显示在”make menuconfig“中。
2.2.5、第31行”define Build/Prepare“
编译准备方法,对于网上下载的软件包不需要再描述。对于非网上下载或自行开发的软件包必须说明编译准备方法。本文所用的准备方法就是首先创建软件包目录,然后将源码拷贝到刚刚创建的目录中。按OpenWrt的习惯,一般把自己设计的程序全部放在src目录下。
2.2.6、第36行"define Build/Configure”
Build/Configure:在Automake中需要进行“./configure”,所以本配置方法主要针对需要配置的软件包而设计,一般自行开发的软件包可以不在这里说明。本文设计的package由自己写makefile,所以此处没有定义。
2.2.7、第39行”define Build/Compile“
编译方法,没有特别说明的可以不予以定义。如果不定义将使用默认的编译方法Build/Compile/Default。
自行开发的软件包可以考虑使用下面的定义:
点击(此处)折叠或打开
- define Build/Compile
- $(MAKE) -C $(PKG_BUILD_DIR) \
- $(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I$(LINUX_DIR)/include"
- Endef
本文此处指定了交叉编译器和体系结构。
2.2.8、第46行”define Package/luci-app-szloogson/install“
软件包的安装方法,包括一系列拷贝编译好的文件到指定位置。调用时会带一个参数,就是嵌入系统的镜像文件系统目录,因此$(1)表示嵌入系统的镜像目录。一般可以采用下面的方法:
点击(此处)折叠或打开
- define Package/luci-app-szloogson/install
- $(INSTALL_DIR) $(1)/usr/bin
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/gsc3280_led $(1)/usr/sbin/
- endef
INSTALL_DIR、INSTALL_BIN在”$(TOPDIR)/rules.mk“文件中定义,所以本Makefile必须引入$(TOPDIR)/rules.mk文件。
INSTALL_DIR :=install -d -m0755:创建所属用戶可读写、执行,其他用戶可读可执行的目录。
INSTALL_BIN:=install -m0755:编译好的文件到镜像文件目录。
安装文件放在files子目录下,不要与源代码文件目录“src”混在一起,以提高可读性。
如果用户态软件在boot时要自动运行,则需要在安装方法说明中增加自动运行的脚本文件安装和配置文件安裝方法。
例如:
点击(此处)折叠或打开
- define Package/luci-app-szloogson/install
- #install shell
- $(INSTALL_DIR) $(1)/etc/init.d
- $(INSTALL_BIN) ./files/loogson.init $(1)/etc/init.d/loogson
- #install config
- $(INSTALL_DIR) $(1)/etc/config
- $(INSTALL_CONF) ./files/loogson.config $(1)/etc/config/loogson
- endef
2.2.9、第66行”$(eval $(call BuildPackage,luci-app-szloogson))“
2.2.10、本文没有用到的
Package/$(PKG_NAME)/conffiles:本包安装的配置文件,一行一个。如果文件结尾使用“/”,则表示为目录。用于备份配置文件说明,在sysupgrade命令执行时将会用到。
AUTOLOAD:=$(call AutoLoad, $(PRIORITY),$(AUTOLOAD_MODS))
用户态的软件包中沒有内核模块的“AUTOLOAD”参数。如果软件需要在boot时自动运行,则需要在“/etc/init.d”增加相应的脚本文件。 脚本文件需要START参数,说明在boot时的优先级,如果在boot启动后再启动,则需要STOP参数。如果STOP参数存在,其值必须大于START。由“/etc/rc.d/S10boot”知道,装载内核模块的优先级为10,需要使用自己设计的内核模块的程序其START的值必须大于10。同样由“/etc/rc.d/S40network”知道,使用网络通信的程序其START的值必须大于40。
2.4、脚本文件
脚本文件需要start()和stop()两个函数,start()是执行程序,stop()是关闭程序。关闭程序一般需要执行killall命令。
在(一)中我们讨论了点击“应用”后执行的脚本文件在“/etc/init.d/loogson”目录下,程序如下:
点击(此处)折叠或打开
- #!/bin/sh /etc/rc.common
- # (C) 2014 openwrt.org
- # add by Davied Huang <apple_guet@126.com>
- START=50
- LED_BIN="/usr/sbin/gsc3280_led"
- control_board()
- {
- local ledstatus, lednum;
- config_get_bool ledstatus $1 ledstatus
- config_get lednum $1 lednum
- echo "${lednum} ${ledstatus}"
- ${LED_BIN} ${lednum} ${ledstatus}
- }
- start() {
- config_load loogson
- config_foreach control_board controlboard
- }
- stop() {
- config_load loogson
- #config_foreach stop_instance controlboard
- }
说明:
1) 在“start”函数中,首先使用“config_load”函数加载“/etc/config/”目录下的loogson配置文件。
2) “config_foreach”遍历"/etc/init.d/loogson"配置文件中的Section,并且执行"control_board"函数。
3) 在"control_board"函数中,使用“config_get_bool”获得操作LED的开关状态,config_get获得操作第几个LED。
4) "config_load"、“config_foreach”、“config_get”和“config_get_bool”等函数由其他脚本提供,可以直接使用。
5) 最后执行可执行文件,后面加上可执行文件需要的参数。该可执行文件的源码为:
点击(此处)折叠或打开
- #include<stdio.h>
- #include<unistd.h>
- #include<fcntl.h>
- #include<asm/ioctl.h>
- int main(int argc,char *argv[])
- {
- int fd;
- if (argc != 3) {
- printf("wrong cmd!\n");
- }
- fd = open("/dev/led", O_RDWR);
- if(fd == -1){
- printf("open led failed!\n");
- }
- ioctl(fd, argv[1], argv[2]);
- close(fd);
- return 0;
- }
三、参考资料
官方说明文档: http://luci.subsignal.org/trac/wiki/Documentation
LuCI上配置Makefile: http://luci.subsignal.org/trac/wiki/Documentation/Modules
CBI文档: http://luci.subsignal.org/trac/wiki/Documentation/CBI
Luci模块说明文档: http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo
Luci类库的函数定义和使用说明: http://luci.subsignal.org/api/luci/index.html
UCI接口在脚本文件中的官方说明: http://wiki.openwrt.org/doc/devel/config-scripting
转:openwrt中luci学习笔记的更多相关文章
- VS2013中Python学习笔记[Django Web的第一个网页]
前言 前面我简单介绍了Python的Hello World.看到有人问我搞搞Python的Web,一时兴起,就来试试看. 第一篇 VS2013中Python学习笔记[环境搭建] 简单介绍Python环 ...
- VS2013中Python学习笔记[环境搭建]
前言 Python是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言. Python的设计具有很强的可读性,相比其他语言经常使用英文关键字,其他语言的一些标点符号,它具有比其他语言更有特色 ...
- spring 中bean学习笔记
spring 中bean 一.bean的定义和应用 1. bean 形象上类似于getXX()和setXX()的一种. 2. 由于java是面向对象的,类的方法和属性在使用中需要实例化. 3. 规律: ...
- php中traits学习笔记
traits学习 越来越多的框架和代码开始使用traits方式去组织一些功能,这是非常高效的代码组织结构. 通过trait来减少不必要的类继承关系,让代码更加复用,形成可以拔插的代码集合. 通过逗号分 ...
- VS2013中Python学习笔记[基础入门]
前言 在上一节中简单的介绍了在VS2013中如何进行开发Hello World,在VS2013中进行搭建了环境http://www.cnblogs.com/aehyok/p/3986168.html. ...
- java中log4j学习笔记
Log4j是apache的一个开源项目,用来操作程序日志信息的框架.因便于管理,在工程中用来代替System.out打印语句.通过配置Log4j中的log4j.properties,可以指定日志信息的 ...
- Java中ArrayList学习笔记
1. 先看两段代码 这段代码在执行的时候会报 但是这样写就好着呢: 总结,研究报错的代码 ,在for循环的时候调用next()方法,next方法中调用了checkForComodification这个 ...
- [Union]C++中Union学习笔记
C++ union结构式一种特殊的类.它能够包含访问权限.成员变量.成员函数(可以包含构造函数和析构函数).它不能包含虚函数和静态数据变量.它也不能被用作其他类的基类,它本身也不能有从某个基类派生而来 ...
- web自动化测试中接口测试学习笔记
一.web基础 web是实现:客户端浏览器端<—————>服务端 交互的应用: web通常包含两部分:web客户端.web服务端:web客户端技术包含html.javascript.aj ...
随机推荐
- display:inline-block 去除间隙
先写出代码 核心css代码: .sample0{display: inline-block;height: 40px;width: 40px;color: #ffffff;background-col ...
- DOM样式操作
CSS 到 DOM的抽象 通过操作 CSS 对应的 DOM对象来更新CSS样式 换肤操作 如何获取实际的样式(不仅有行内,更有页面和外联样式表中定义的样式) 样式表分为三类: 外联,页面,行内 内部样 ...
- Git中文版教程
1. 起步 1.1 关于版本控制 1.2 Git 简史 1.3 Git 基础 1.4 命令行 1.5 安装 Git 1.6 初次运行 Git 前的配置 1.7 获取帮助 1.8 总结 2. Git 基 ...
- 中控考勤仪IFace302多线程操作时无法订阅事件
场景: 在各办事点安装中控考勤仪Iface302,各办事点的工作人员上下班报到时使用指纹或面纹进行自动登记,验证成功后将与服务吕进行通讯记录相关的考勤信息. 条件限制: 由于Iface302设备不支持 ...
- easy ui 1.4的NumberBox,失去焦点后不能再次输入小数点
这也是1.4版本的bug,现在1.4.1也发布了,经验证,该问题在新版本中已经解决了 在网上找到的解决办法,地址:http://www.jeasyui.com/forum/index.php?topi ...
- yii2开发后记
h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h ...
- 烂泥:CentOS6.5挂载windows共享文件夹
本文由秀依林枫提供友情赞助,首发于烂泥行天下. 由于工作需要,需要把本机的文件夹共享出去,然后让CentOS服务器临时使用下. 服务器使用的是CentOS系统,而本机使用的win7系统.考虑到是临时使 ...
- 不错的flash,动漫,小插件小集
(来源:http://abowman.com/google-modules/) embed code <object width="220" height="166 ...
- subversion 1.8.5好像不是很成熟
加:--enable-all-static1. 没找到libneon库支持,不得不用libserf。2. libserf编译要用到scons.py,所有要有python工具支持。3. 当遇到链接少ss ...
- node命令