2017年11月5日 星期日

MQTT


MQ Telemetry Transport (MQTT)網路文章已經很多,也很完善,大家可以參考本文章後面的reference,這裡快速帶過幾個重點:
MQTT是一個輕量級的基於broker的Publish/Subscribe messaging protocol,旨在實現開放,簡單,輕量級和易於實現。
協議的特點包括:
  • 提供一對多的訊息發布
  • 三種QoS
    "At most once" - message丟失或重複可能發生。
    "At least once" - 確保message到達,但可能會發生重複。
    "Exactly once" - 確保message只送達一次。

MQTT Architecture(Publish/Subscribe with broker)

MQTT有三種主要的組成元件,分別為Publisher、Subscriber以及Broker。 Publisher為訊息的來源,它會將訊息(Topic)發送給Broker,而Subscriber向Broker註冊,表示他們想要接收某Topic訊息;因此當有某個Publisher對Broker發送Topic訊息時,只要是有對此Topic註冊的Subscriber,都會收到此則訊息。

Topic wildcards

Topic為UTF-8編碼字串,最長長度為32,767字元,Topic支援階層式命名方式,如:"住家/客廳/溫度",階層的分隔符號為"/",所以前面這個Topic有三層架構,Topic可以透過萬用字元一次訂閱多個主題。但是這些萬用字元只能放在最後一層
"#"為Multi-level wildcard,可以包含零個以上的階層,如訂閱"finance/stock/ibm/#",將包含finance/stock/ibm與 finance/stock/ibm/closingprice和finance/stock/ibm/currentprice
"+"為Single-level wildcard,只能包含該層的Topic,如訂閱"finance/stock/+",將包含finance/stock/ibm與finance/stock/xyz但不包含finance/stock/ibm/closingprice

Deom the MQTT - mosquitto

請用apt-get install mosquitto mosquitto-clients安裝mosquitto套件
run the broker
brook@vista:~$ mosquitto -v
1511094611: mosquitto version 1.4.8 (build date Mon, 26 Jun 2017 09:31:02 +0100) starting
1511094611: Using default config.
1511094611: Opening ipv4 listen socket on port 1883.
1511094611: Opening ipv6 listen socket on port 1883.
1511094635: New connection from 127.0.0.1 on port 1883.
1511094635: New client connected from 127.0.0.1 as mosqsub/16408-jpr-Verit (c1, k60). 有人連上就會顯示
1511094635: Sending CONNACK to mosqsub/16408-jpr-Verit (0, 0)
1511094635: Received SUBSCRIBE from mosqsub/16408-jpr-Verit
1511094635:     /brook/L1 (QoS 0)
1511094635: mosqsub/16408-jpr-Verit 0 /brook/L1
1511094635: Sending SUBACK to mosqsub/16408-jpr-Verit
1511094659: New connection from 127.0.0.1 on port 1883.
1511094659: New client connected from 127.0.0.1 as mosqpub/16687-jpr-Verit (c1, k60).
1511094659: Sending CONNACK to mosqpub/16687-jpr-Verit (0, 0)
1511094659: Received PUBLISH from mosqpub/16687-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Sending PUBLISH to mosqsub/16408-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Received DISCONNECT from mosqpub/16687-jpr-Verit
1511094659: Client mosqpub/16687-jpr-Verit disconnected.

向Broker註冊topic "/brook/L1"
brook@vista:~$ mosquitto_sub -h localhost -t '/brook/L1'
test for L1有人向Broker推送/brook/L1訊息就會顯示

向Broker推送topic "/brook/L1"的訊息
brook@vista:~$ mosquitto_pub -h localhost -t '/brook/L1' -m 'test for L1'

mosquitto - Broker log notes

brook@vista:~$ mosquitto -v
1511094635: New client connected from 127.0.0.1 as mosqsub/16408-jpr-Verit (c1, k60). 

mosquitto-1.4.8/src/read_handle_server.c
521 if(context->username){
522   _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, 
                           "New client connected from %s as %s (c%d, k%d, u'%s').",
                            context->address, client_id, clean_session, 
                            context->keepalive, context->username);
523 }else{
524   _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE,
                           "New client connected from %s as %s (c%d, k%d).", 
                           context->address, client_id, clean_session,
                           context->keepalive);
525 }

1511094635: Sending CONNACK to mosqsub/16408-jpr-Verit (0, 0)
1511094635: Received SUBSCRIBE from mosqsub/16408-jpr-Verit
1511094635:     /brook/L1 (QoS 0)

man mqtt
QUALITY OF SERVICE
  MQTT defines three levels of Quality of Service (QoS). The QoS defines how hard the 
  broker/client will try to ensure that a message is received. Messages may be sent 
  at any QoS level, and clients may attempt to subscribe to topics at any QoS level.
  This means that the client chooses the maximum QoS it will receive. For example, 
  if a message is published at QoS 2 and a client is subscribed with QoS 0, the 
  message will be delivered to that client with QoS 0. If a second client is also 
  subscribed to the same topic, but with QoS 2, then it will receive the same message 
  but with QoS 2. For a second example, if a client is subscribed with QoS 2 and a 
  message is published on QoS 0, the client will receive it on QoS 0.

  Higher levels of QoS are more reliable, but involve higher latency and have higher bandwidth requirements.
    o 0: The broker/client will deliver the message once, with no confirmation.
    o 1: The broker/client will deliver the message at least once, with confirmation required.
    o 2: The broker/client will deliver the message exactly once by using a four step handshake.

1511094635: mosqsub/16408-jpr-Verit 0 /brook/L1
1511094635: Sending SUBACK to mosqsub/16408-jpr-Verit
1511094659: New connection from 127.0.0.1 on port 1883.
1511094659: New client connected from 127.0.0.1 as mosqpub/16687-jpr-Verit (c1, k60).
1511094659: Sending CONNACK to mosqpub/16687-jpr-Verit (0, 0)
1511094659: Received PUBLISH from mosqpub/16687-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Sending PUBLISH to mosqsub/16408-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Received DISCONNECT from mosqpub/16687-jpr-Verit
1511094659: Client mosqpub/16687-jpr-Verit disconnected.


2017年7月29日 星期六

A pattern for state machine


State machine是很常見的應用/Pattern,這章節會根據下面的圖來實作State machine pattern


enum state {
 STATE_INITIAL = 0,
 STATE_1,
 STATE_2,
 STATE_3,
 STATE_4,
};

enum event {
 E1 = 1,
 E2,
 E3,
 E4,
};
先定義State Machine狀態與Event



struct instance_data {
 enum event evt;
 int data;
};

struct instance {
 enum state cur_state;
 struct instance_data data;
};
定義一個struct來儲存"現在狀態",Event與data



typedef enum state state_func_t(struct instance_data *data);

state_func_t* const state_table[] = {
 [STATE_INITIAL] = in_init,
 [STATE_1] = in_state_1,
 [STATE_2] = in_state_2,
 [STATE_3] = in_state_3,
 [STATE_4] = in_state_4,
};

void run_state(struct instance *inst)
{
 inst->cur_state = state_table[inst->cur_state](&(inst->data));
};

建立一個Table,將每一個state對應的function填入,run_state()會根據目前的State處理該event/data,並回傳下一個狀態



enum state in_state_1(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E1:
  printf("change to S2\n");
  return do_s2();
 case E2:
  printf("change to S3\n");
  return do_s3();
 default:
  printf("keep the same STATE && do nothing\n");
  return STATE_1;
 }
}

enum state in_state_2(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E3:
  printf("change to S3\n");
  return do_s3();
 default:
  printf("keep the same STATE && do s2 again\n");
  return do_s2(); // do s2 again
 }
}

enum state in_state_3(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E2:
  printf("change to S4\n");
  return do_s4();
 default:
  printf("keep the same STATE && do nothing\n");
  return STATE_3;
 }
}

enum state in_state_4(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E1:
  printf("change to S2\n");
  return do_s2();
 case E3:
  printf("change to S1\n");
  return do_s1();
 default:
  printf("keep the same STATE && do again\n");
  return do_s4();
 }
}
定義每一個狀態中的行為



enum state do_s1(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_1;
}

enum state do_s2(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_2;
}

enum state do_s3(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_3;
}

enum state do_s4(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_4;
}

enum state in_init(struct instance_data *data)
{
 printf("%s(#%d): do some init. E:%d\n", __FUNCTION__, __LINE__, data->evt);
 printf("change to S1\n");
 return STATE_1;
}
定義進入每一個狀態要執行的動作



int main( void ) {
 int ch;
 struct instance inst = {STATE_INITIAL, 0};
 while ( 1 ) {
  run_state(&inst);
  // do other program logic, run other state machines, etc
  printf("MENU: E1/E2/E3/E4\n");
  while(((ch = getc(stdin)) == '\n') || (ch <= '0') || (ch > '4'));
  inst.data.evt = ch - '0';
 }
}
main function模擬State Machine收到不同event(1~4)


以下是收到event 1, 2, 3, 4的執行結果





2017年7月8日 星期六

How to send a html email with the bash command "mail"/“sendmail”?


有時我們會需要透過Linux發送一些report mail,這時候我們就可以用mail這個指令
brook@vista:~/kernel$ ls | txt2html| mail --debug-level=7 -s 'show kernel floder' rene3210@gmail.com
mail: sendmail (/usr/sbin/sendmailn
mail: Getting auth info for UID 1003
mail: source=system, name=brook, passwd=x, uid=1003, gid=1000, gecos=,,,, dir=/home/brook, shell=/bin/bash, mailbox=/var/mail/brook, quota=0, change_uid=1
mail: Getting auth info for UID 1003
mail: source=system, name=brook, passwd=x, uid=1003, gid=1000, gecos=,,,, dir=/home/brook, shell=/bin/bash, mailbox=/var/mail/brook, quota=0, change_uid=1
mail: mu_mailer_send_message(): using From: brook@vista
mail: Sending headers...
mail: Sending body...
mail: /usr/sbin/sendmail exited with: 0
mail:

上面這個指令會送出raw data,mail看到的也是顯示一些HTML內容,如
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="generator" content="HTML::TextToHTML v2.51"/>
</head>
<body>
<p>AndroidKernel.mk<br/>
<strong>COPYING</strong><br/>
<strong>CREDITS</strong><br/>
Documentation<br/>
Kbuild<br/>
Kconfig<br/>
<strong>MAINTAINERS</strong><br/>
Makefile<br/>
<strong>README</strong><br/>
<strong>REPORTING-BUGS</strong><br/>
android<br/>
arch<br/>
block<br/>
crypto<br/>
drivers<br/>
firmware<br/>
fs<br/>
include<br/>
init<br/>
ipc<br/>
kernel<br/>
lib<br/>
linaro<br/>
mm<br/>
net<br/>
samples<br/>
scripts<br/>
security<br/>
sound<br/>
tools<br/>
usr<br/>
virt</p>

</body>
</html>

如果我們要支援"Multipurpose Internet Mail Extensions (MIME)"只要在subject後面接上"\nContent-Type: text/html"即可
brook@vista:~/kernel$ ls | txt2html| mail --debug-level=7 -s "$(echo -e "show kernel floder\nContent-Type: text/html")" rene3210@gmail.com

也可以用"sendmail"指令來傳送mail
(
echo "From: ${from}";
echo "To: ${to}";
echo "Subject: ${subject}";
echo "Content-Type: text/html";
echo "MIME-Version: 1.0";
echo "";
echo "${message}";
) | sendmail -t

(message=`ls| txt2html`
echo "From: rene3210@gmail.com;"
echo "To: rene3210@gmail.com,rene3210@gov.tw;"
echo "Subject: Quota"
echo "Content-Type: text/html;"
echo "MIME-Version: 1.0;"
echo "${message}";
echo -e"\n\n"
) |sendmail -t


參考資料: How to send a html email with the bash command “sendmail”?



2017年4月30日 星期日

pkg-config


簡介

現在的電腦系統使用許多library package供使用者使用,但是在不同的platform使用這些library package是一件很困難的事,pkg-config收集相關的資訊並且統一的API供開發人員使用。 pkg-config利用".pc"檔的格式,記錄了一些library相關的資訊,提供對應的information給開發人員使用,以ubuntu為例,這些檔案被放置在/usr/lib/x86_64-linux-gnu/pkgconfig、/usr/lib/i386-linux-gnu/pkgconfig與/usr/lib/pkgconfig等目錄,以下為directfb的.pc
brook@vista:~$ cat /usr/lib/x86_64-linux-gnu/pkgconfig/directfb.pc
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include

Name: DirectFB
Description: Graphics and windowing library for the Linux frame buffer device
Version: 1.2.10
Requires: fusion direct
Libs: -L${libdir} -ldirectfb -lpthread
Libs.private: -L${libdir} -ldl
Cflags: -D_REENTRANT -I${prefix}/include/directfb
brook@vista:~$ pkg-config --list-all
file_name_of_library_or_package          Name_in_pc - Description_in_pc
...
direct                         Direct - DirectFB base development library
...

PC檔內容

.pc包含了事先定義好的variable(key=value)與description(Name: description),description有以下資訊
Name: library的name。
Description: library的簡述。
URL: library的相關URL。
Version: library的版號資訊。
Requires: library所需要的library,可能會包含所需的版號資訊(=, <, >, <= or >=)。
Requires.private: 與 Requires相似,用於static link。
Conflicts: library可能與某個library衝突的資訊,可能會包含衝突的版號資訊(=, <, >, <= or >=)。
Cflags: 使用該library所需的compiler flags。
Libs: 使用該library所需的link flags。
Libs.private: 與Libs相似,用於static link。

pkg-config操作

基本語法為pkg-config [input parameters] [LIBRARIES...]相關input參數可以參考man page
brook@vista:~$ pkg-config --modversion --print-errors directfb
1.2.10
brook@vista:~$ pkg-config --cflags --print-errors directfb
-D_REENTRANT -I/usr/include/directfb
brook@vista:~$ pkg-config --libs --print-errors directfb
-ldirectfb -lpthread -lfusion -ldirect -lpthread
brook@vista:~$ pkg-config --libs --static --print-errors directfb
-ldirectfb -lpthread -ldl -lfusion -ldirect -lpthread -ldl
brook@vista:~$ pkg-config --print-requires --print-errors directfb
fusion
direct
brook@vista:~$ pkg-config --print-requires --print-errors "directfb > 1.3"
Requested 'directfb > 1.3' but version of DirectFB is 1.2.10
brook@vista:~$ pkg-config --exists --print-errors directfb
brook@vista:~$ echo $?
0
brook@vista:~$ pkg-config --exists --print-errors directfbxx
Package directfbxx was not found in the pkg-config search path.
Perhaps you should add the directory containing `directfbxx.pc'
to the PKG_CONFIG_PATH environment variable
No package 'directfbxx' found
brook@vista:~$ echo $?
1

pkg-config實際應用

With GCC
brook@vista:~$ pkg-config --cflags --libs directfb
-D_REENTRANT -I/usr/include/directfb -ldirectfb -lpthread -lfusion -ldirect -lpthread
brook@vista:~$ gcc `pkg-config --cflags --libs directfb` -o myapp myapp.c

With autoconf and automake
configure.ac:
PKG_CHECK_MODULES([DIRECTFB], [directfb])

Makefile.am:
myapp_CFLAGS = $(DIRECTFB_CFLAGS)
myapp_LDADD = $(DIRECTFB_LIBS)

    參考資料:
  1. 簡介 pkg-config 的功能與用法, http://yczhuang.blogspot.tw/2007/04/pkg-config.html
  2. Guide to pkg-config, https://people.freedesktop.org/~dbn/pkg-config-guide.html#writing
  3. pkg-config 使用及建立方法, http://jyhshin.pixnet.net/blog/post/26588033-pkg-config-%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%BB%BA%E7%AB%8B%E6%96%B9%E6%B3%95