15
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

日本Androidの会秋葉原支部ロボット部Advent Calendar 2018

Day 13

Felica でつくる簡単な入退室管理 (Rasbpberry Pi に複数台のカードリーダ)

Last updated at Posted at 2017-12-01

NFCでやってみた実験一覧

Sony のちょっと旧いカードリーダー、S320。
Felica を使って簡単な入退室管理。

Suica や PASMO を使って、例えば着席したときに机上のカードリーダに読み込ませて・・・という利用シーンがある。この場合は、PCとかにカードリーダを1台つなげていればいい。

けれども入室するときに Suica 使って解錠すると同時に記録を取る、なんてことをする場合は PC じゃなくて IoT デバイス・・・今回は Raspberry Pi に繋げておきたい。
そして部屋の中と外にカードリーダーを設置し、入室専用と退室専用としたいですね。カードリーダーは2台当然必要だけれども、Raspberry Pi は1台にしたいところ。

ということで、Raspberry Pi にカードリーダーを2つつなげる実験。

Ubuntu 16.04 ( AMDx64 ) で S320 を動かす

まずは、PCを使ってテスト。

あらかじめ、


apt-get install libusb-dev

しておく

カードリーダーによって必要になるライブラリは異なる。

http://paralleltree.hatenablog.com/entry/2017/04/01/205708
によると、libpafeが対応しているということなので

http://hito.music.coocan.jp/pasori/libpafe.html
から
libpafe_0.0.8-1_amd64.deb
をダウンロードしてインストールした。


$ pasori_test 
PaSoRi (RC-S320)
 firmware version 1.40
Echo test... success
EPROM test... success
RAM test... success
CPU test... success
Polling test... success

しかしながら、

pasori.pyというのを作ってみて、


$ python3 pasori.py
Traceback (most recent call last):
  File "pasori.py", line 11, in <module>
    libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")
  File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
    return self._dlltype(name)
  File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: /usr/local/lib/libpafe.so: cannot open shared object file: No such file or directory

となる。

sudo dpkg -i libpafe_0.0.8-1_amd64.deb
とかしてもダメだった。
あれれ?

結局、

の通り


$ sudo ldconfig

すると


$ python3 pasori.py 
08ED125012345678

動いた(数字はダミー)

Raspberry pi ( raspbian )


# uname -a
Linux raspberrypi 4.9.24-v7+ #993 SMP Wed Apr 26 18:01:23 BST 2017 armv7l GNU/Linux

ちょっと前のraspbian

Ubuntuと同様に


sudo apt-get install libusb-dev

しておく

http://hito.music.coocan.jp/pasori/libpafe.html から辿って
wget http://hito.music.coocan.jp/pasori/libpafe-0.0.8.tar.gz
をダウンロード

解凍したディレクトリに入って


./configure
make
sudo make install

としたが、


 $ pasori_test
pasori_test: error while loading shared libraries: libpafe.so.0: cannot open shared object file: No such file or directory

となる。しかしながらUbuntuと同じく


$ sudo ldconfig

でこの問題はクリア。次に、


 $ pasori_test
error

となる。この問題はudevに登録してなかったため。

https://github.com/rfujita/libpafe
のドキュメントを参考に


$ sudo vim /lib/udev/rules.d/60-libpafe.rules

ACTION!="add", GOTO="pasori_rules_end"
SUBSYSTEM=="usb_device", GOTO="pasori_rules_start"
SUBSYSTEM!="usb", GOTO="pasori_rules_end"
LABEL="pasori_rules_start"

ATTRS{idVendor}=="054c", ATTRS{idProduct}=="01bb", MODE="0664", GROUP="plugdev"
ATTRS{idVendor}=="054c", ATTRS{idProduct}=="02e1", MODE="0664", GROUP="plugdev"

LABEL="pasori_rules_end"

$ sudo udevadm control --reload-rules

とすると使えるようになる

GoogleCalendarに記録する

IFTTT

IFTTT使ってGoogleCalendarに登録しようとしたけどうまくいかない。
いい感じにWebHookでトリガーかけたつもりだけれどGoogleCalendar側には何も出ない。
デバッグとかも難しそうだったのでさくっと別方法を。

gcalcli インストールの試行錯誤

ここの項目は、インストールに苦労した記録。単にインストールしたいひとはここは飛ばしてください。


$ sudo pip3 install gcalcli
Downloading/unpacking gcalcli
  Downloading gcalcli-3.4.0.tar.gz (1.7MB): 1.7MB downloaded
  Running setup.py (path:/tmp/pip-build-9snj57_o/gcalcli/setup.py) 
・
・
・

さて、試してみるか・・・


$ gcalcli add
  File "/usr/local/bin/gcalcli", line 199
    print "ERROR: Missing module - %s" % e.args[0]
                                     ^

うーん?? Python2 書式でエラーになってますね。
さて、raspbian のレポジトリにも gcalcli 入っているのでこちらを試してみましょう。


$ sudo apt-get install gcalcli
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の追加パッケージがインストールされます:
  fontconfig fontconfig-config fonts-dejavu-core gxmessage hicolor-icon-theme
  javascript-common libatk1.0-0 libatk1.0-data libavahi-client3 libcairo2
  libcups2 libdatrie1 libfontconfig1 libgdk-pixbuf2.0-0
  libgdk-pixbuf2.0-common libgraphite2-3 libgtk2.0-0 libgtk2.0-bin
  libgtk2.0-common libharfbuzz0b libjasper1 libjs-jquery libpango-1.0-0
  libpango1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 libpangox-1.0-0
  libpangoxft-1.0-0 libpixman-1-0 libthai-data libthai0 libxcb-render0
  libxcb-shm0 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 libxft2
  libxinerama1 libxrandr2 libxrender1 python-crypto python-dateutil
  python-gdata python-parsedatetime python-pyicu python-simplejson python-six
  python-vobject
提案パッケージ:
  cups-common librsvg2-common gvfs libjasper-runtime python-crypto-dbg
  python-crypto-doc python-gdata-doc python-twisted-core
以下のパッケージが新たにインストールされます:
  fontconfig fontconfig-config fonts-dejavu-core gcalcli gxmessage
  hicolor-icon-theme javascript-common libatk1.0-0 libatk1.0-data
  libavahi-client3 libcairo2 libcups2 libdatrie1 libfontconfig1
  libgdk-pixbuf2.0-0 libgdk-pixbuf2.0-common libgraphite2-3 libgtk2.0-0
  libgtk2.0-bin libgtk2.0-common libharfbuzz0b libjasper1 libjs-jquery
  libpango-1.0-0 libpango1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0
  libpangox-1.0-0 libpangoxft-1.0-0 libpixman-1-0 libthai-data libthai0
  libxcb-render0 libxcb-shm0 libxcomposite1 libxcursor1 libxdamage1 libxfixes3
  libxft2 libxinerama1 libxrandr2 libxrender1 python-crypto python-dateutil
  python-gdata python-parsedatetime python-pyicu python-simplejson python-six
  python-vobject
アップグレード: 0 個、新規インストール: 50 個、削除: 0 個、保留: 0 個。
14.6 MB のアーカイブを取得する必要があります。
この操作後に追加で 52.2 MB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main fonts-dejavu-core all 2.34-1 [1,047 kB]
取得:2 http://archive.raspberrypi.org/debian/ jessie/main libpixman-1-0 armhf 0.33.3+git20151011-7de61d8-rpi1 [156 kB]
取得:3 http://mirrordirector.raspbian.org/raspbian/ jessie/main fontconfig-config all 2.11.0-6.3+deb8u1 [274 kB]
・
・
・

$ gcalcli
  File "/usr/local/bin/gcalcli", line 199
    print "ERROR: Missing module - %s" % e.args[0]
                                     ^
SyntaxError: Missing parentheses in call to 'print'

うーん? pathが?


$ sudo pip3 uninstall gcalcli
$ sudo apt-get purge gcalcli
$ sudo pip3 uninstall gcalcli
$ sudo pip install gcalcli

とか繰り返していても同じエラー。はてなー? と思って /usr/local/bin/gcalcliを消してた上で改めて

$ sudo pip install gcalcli

すると動くようになった

(後から考えると $ hash -r するだけで多分OK)

改めて、 gcalcli インストールのまとめ

以下だけでOK(多分)


sudo apt-get install python-pip
sudo pip install gcalcli

gcalcli

まず最初に

$ gcalcli -noauth_local_webserver list
WARNING:root:This function, oauth2client.tools.run(), and the use of the gflags library are deprecated and will be removed in a future version of the library.
Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Furlshortener&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=232867676714.apps.googleusercontent.com&access_type=offline

Enter verification code:

と出るので、この URL をブラウザにコピペ。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3133393532342f62643764343433642d386538372d306130322d376431382d6363663332396162313166372e706e67.png
??

出てきたvarification codeをコピペ。

image

こんな感じで登録できた。

入室を記録

予定を登録するテスト


$ gcalcli --calendar 'arrived' --title 'TEST' --where 'オフィス' --when '11/30/2017 17:00' --duration 60 --description 'ついた' --reminder 30 add

こうすると、2017/11/30 17:00-17:30 の予定として、arrivedカレンダーに「オフィス」に「ついた」として登録できた。

試しに時刻を"now"とすると、

$ gcalcli --calendar 'arrived' --title 'TEST' --where 'オフィス' --when 'now' --duration 60 --description 'ついた' --reminder 30 add

現在時刻で登録できた。

pythonで、特定のカードがかざされるとgcalcliを呼び出すスクリプトを作って、「入室」はできた。

退室

どうしようかな・・・とりあえず安直に、カードリーダを2つ繋げて入室専用と退室専用としてみます。

RaspberryPiのUSBポートはこんな感じです。


$ lsusb -t 
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=dwc_otg/1p, 480M
    |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/5p, 480M
        |__ Port 1: Dev 3, If 0, Class=Vendor Specific Class, Driver=smsc95xx, 480M
        |__ Port 2: Dev 12, If 0, Class=Vendor Specific Class, Driver=, 1.5M
        |__ Port 5: Dev 14, If 0, Class=Vendor Specific Class, Driver=, 1.5M

4ポートあるUSBコネクタは、内部ではBus01.Dev.2の配下の Port2,Port3,Port4,Port5となっていました。ちなみに、Port1は内部でオンボードのネットワークインターフェースに割り当てられています。

libpafeの標準では、どちらか1つしか認識しません。

ソースを見ていると、はてな? 同じような処理が二重に書かれているのはどうしてかな・・・

調べてみると、libusbというのは2種類あるらしいです。

libusb-dev と libusb-1.0-0の違い

libusb-dev

インストール方法:sudo apt-get install libusb-dev
説明はこちら http://www.electric-spoon.com/doc/libusb-dev/html/index.html

usb.hをインクルードして使用

対応

Chapter 2. Current OS support
Linux (2.2, 2.4 and on)
FreeBSD, NetBSD and OpenBSD
Darwin/MacOS X

libusb-1.0-0

インストール方法:sudo apt-get install libusb-1.0-0-dev
説明はこっち http://libusb.sourceforge.net/

libusb.hをインクルードして使用

対応

What platforms are supported?
Linux, OS X, Windows, Windows CE, Android, OpenBSD/NetBSD, Haiku.

この説明はlibusb-1.0-0の方(多分)
「libusb関係のメモと、バルク転送サンプル」
https://qiita.com/gpsnmeajp/items/b1282b2d3c14470bbae7

libpafe の実装

libpafeは、#ifdef HAVE_LIBUSB_1 でどちらを使うかを分けています。
デフォルトだとlibusb-devを使うようになっていました。

libpafeを書き換えて複数のpasoriに対応する

とりあえず、libusb-devを使う前提で、複数のpasoriに対応するようにしてみました。

pasori_command.hのdiff


6d5
< pasori *pasori_open_n(int number);

pasori_command.cのdiff


10a11
> 
714,863d714
< open_usb_n(pasori *pp, int number)
< {
< #ifdef HAVE_LIBUSB_1
<   int i, r, cnt;
<   struct libusb_device_descriptor desc;
<   libusb_device *dev;
< 
<   pp->ctx = NULL;
<   pp->devs = NULL;
<   r = libusb_init(&pp->ctx);
<   if (r < 0) {
<     return PASORI_ERR_COM;
<   }
< #ifdef DEBUG_USB
<   libusb_set_debug(pp->ctx, 3);
< #endif
<   cnt = libusb_get_device_list(pp->ctx, &pp->devs); //get the list of devices
<   if(cnt < 0) {
<     return PASORI_ERR_COM;
<   }
< 
<   for(i = 0; i < cnt; i++) {
<     r = libusb_get_device_descriptor(pp->devs[i], &desc);
<     if (r < 0) {
<       continue;
<     }
< 
< #ifdef DEBUG_USB
<     Log("Check for %04x:%04x\n", desc.idVendor, desc.idProduct);	/* debug */
< #endif
<     if (desc.idVendor == PASORIUSB_VENDOR &&
< 	(desc.idProduct == PASORIUSB_PRODUCT_S310 ||
< 	 desc.idProduct == PASORIUSB_PRODUCT_S320 ||
< 	 desc.idProduct == PASORIUSB_PRODUCT_S330)) {
< #ifdef DEBUG_USB
<       Log("Device is found %04x:%04x\n", desc.idVendor, desc.idProduct);	/* debug */
< #endif
<       dev = pp->devs[i];
<       goto finish;
<     }
<   }
<   Log("pasori not found in USB BUS");
<   return PASORI_ERR_COM;
< 
<  finish:
< 
<   switch (desc.idProduct) {
<   case PASORIUSB_PRODUCT_S310:
<     pp->type = PASORI_TYPE_S310;
<     break;
<   case PASORIUSB_PRODUCT_S320:
<     pp->type = PASORI_TYPE_S320;
<     break;
<   case PASORIUSB_PRODUCT_S330:
<     pp->type = PASORI_TYPE_S330;
<     break;
<   default:
<     return PASORI_ERR_TYPE;
<   }
< 
<   r = libusb_open(dev, &pp->dh);
<   if(r) {
<     return PASORI_ERR_COM;
<   }
< 
<   if(libusb_kernel_driver_active(pp->dh, 0) == 1) {
<     r = libusb_detach_kernel_driver(pp->dh, 0);
<     if (r) {
<       return PASORI_ERR_COM;
<     }
<   }
< 
<   pp->timeout = TIMEOUT;
<   get_end_points(pp);
< 
<   if(libusb_claim_interface(pp->dh, INTERFACE_NUMBER) < 0) {
<     return PASORI_ERR_COM;
<   }
< 
<   return 0;
< #else  /* HAVE_LIBUSB_1 */
<   struct usb_bus *bus;
<   struct usb_device *dev;
< 
<   usb_init();
< #ifdef DEBUG_USB
<   usb_set_debug(255);
< #else
<   usb_set_debug(0);
< #endif
<   usb_find_busses();
<   usb_find_devices();
<   int counter=0;
<   for (bus = usb_get_busses(); bus; bus = bus->next) {
<     for (dev = bus->devices; dev; dev = dev->next) {
< #ifdef DEBUG_USB
<       Log("check for %04x:%04x\n", dev->descriptor.idVendor, dev->descriptor.idProduct);	/* debug */
< #endif
<       if (dev->descriptor.idVendor == PASORIUSB_VENDOR &&
< 	  (dev->descriptor.idProduct == PASORIUSB_PRODUCT_S310 ||
< 	   dev->descriptor.idProduct == PASORIUSB_PRODUCT_S320 ||
< 	   dev->descriptor.idProduct == PASORIUSB_PRODUCT_S330)) {
< #ifdef DEBUG_USB
< 	Log("Device is found %04x:%04x\n", dev->descriptor.idVendor, dev->descriptor.idProduct);	/* debug */
< #endif
<          if (counter==number){
<            goto finish;
<          }
<  	 counter++;
<       }
<     }
<   }
<   Log("pasori not found in USB BUS");
<   return PASORI_ERR_COM;
< 
<  finish:
<   switch (dev->descriptor.idProduct) {
<   case PASORIUSB_PRODUCT_S310:
<     pp->type = PASORI_TYPE_S310;
<     break;
<   case PASORIUSB_PRODUCT_S320:
<     pp->type = PASORI_TYPE_S320;
<     break;
<   case PASORIUSB_PRODUCT_S330:
<     pp->type = PASORI_TYPE_S330;
<     break;
<   default:
<     return PASORI_ERR_TYPE;
<   }
< 
<   pp->dh = usb_open(dev);
<   pp->dev = dev;
<   pp->timeout = TIMEOUT;
<   get_end_points(pp);
< 
<   if (usb_set_configuration(pp->dh, 1)) {
<     /* error */
<     return PASORI_ERR_COM;
<   }
< 
<   if (usb_claim_interface
<       (pp->dh, pp->dev->config->interface->altsetting->bInterfaceNumber)) {
<     /* error */
<     return PASORI_ERR_COM;
<   }
<   return 0;
< #endif	/* HAVE_LIBUSB_1 */
< }
< 
< static int
1010,1029d860
< pasori *
< pasori_open_n(int number)
< {
<   pasori *pp;
< 
<   pp = (pasori *) malloc(sizeof(pasori));
< 
<   if (pp == NULL)
<     return NULL;
< 
<   memset(pp, 0, sizeof(pasori));
<   pp->i_ep_in = 0x81;
< 
<   if (open_usb_n(pp,number)) {
<     pasori_close(pp);
<     return NULL;
<   }
< 
<   return pp;
< }


ということで、今までは


  pasori = libpafe.pasori_open_n()

とやればよかったものを


  pasori0 = libpafe.pasori_open_n(0)

というようにすれば今までと同じように使える。


  pasori1 = libpafe.pasori_open_n(1)

とすると2つ目のpasoriにバインドするようになる。
その後の使い方は同じ。

コード


# -*- coding: utf-8 -*-

from __future__ import print_function
from ctypes import *

import time
import wiringpi as w 
import subprocess

FELICA_POLLING_ANY = 0xffff
registered_card = "0123456789012345"


if __name__ == '__main__':

  w.wiringPiSetup()
  w.pinMode(0,1)
  libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")

  libpafe.pasori_open.restype = c_void_p
  libpafe.pasori_open_n.restype = c_void_p
  pasori0 = libpafe.pasori_open_n(0)
  pasori1 = libpafe.pasori_open_n(1)


  while 1:
    mId0 ="0000000000000000"
    mId1 ="0000000000000000"
    while (mId0 == "0000000000000000" and mId1 == "0000000000000000") :
      libpafe.pasori_init(pasori0)
      libpafe.pasori_init(pasori1)
  
      libpafe.felica_polling.restype = c_void_p
      felica0 = libpafe.felica_polling(pasori0, FELICA_POLLING_ANY, 0, 0)
      felica1 = libpafe.felica_polling(pasori1, FELICA_POLLING_ANY, 0, 0)
  
      idm0 = c_ulonglong()
      idm1 = c_ulonglong()
      libpafe.felica_get_idm.restype = c_void_p
      libpafe.felica_get_idm(felica0, byref(idm0))
      libpafe.felica_get_idm(felica1, byref(idm1))
  
      # print('{0:016X}'.format(idm.value ))
      mId0 = '{0:016X}'.format(idm0.value )
      mId1 = '{0:016X}'.format(idm1.value )
#      print (mId0)
#      print (mId1)

      libpafe.free(felica0)
      libpafe.free(felica1)
    if (mId0 == registered_card):
      # registered card
      print("detect arrive:",mId0)
      w.digitalWrite(0,1)
      time.sleep(0.1)
      w.digitalWrite(0,0)
      cmd = " gcalcli --calendar 'arrive' --title 'TEST' --where 'オフィス' --when 'now' --duration 60 --description 'ついた' --reminder 30 add "
      subprocess.call(cmd, shell=True)
    else:
      # non registered card
#      print("detecet not registed:",mId0)
      for i in range(3): 
        w.digitalWrite(0,1)
        time.sleep(0.2)
        w.digitalWrite(0,0)
        time.sleep(0.1)
    if (mId1 == registered_card):
      # registered card
      print("detect leave:",mId1)
      w.digitalWrite(0,1)
      time.sleep(0.1)
      w.digitalWrite(0,0)
      cmd = " gcalcli --calendar 'leave' --title 'TEST' --where 'オフィス' --when 'now' --duration 60 --description 'でた' --reminder 30 add "
      subprocess.call(cmd, shell=True)
    else:
      # non registered card
#      print("detecet not registed:",mId1)
      for i in range(3): 
        w.digitalWrite(0,1)
        time.sleep(0.2)
        w.digitalWrite(0,0)
        time.sleep(0.1)
    time.sleep(1)


  libpafe.pasori_close(pasori0)
  libpafe.pasori_close(pasori1)


kubohiroya 版の libpafe を使う

さて、上記のlibpafeの書き換えについては、rfujita 版の libpafe を使っていましたが、forkしたレポジトリがあり、そちらは複数のpasoriに対応していました。kubohiroya 版の libpafe がそれです。

今回はこのkubohiroya版のlibpafeは使いませんでしたが、raspberry pi上でコンパイルだけやってみました。以下はその記録。


$ ./configure
$ make

・
・
・
WARNING: `aclocal-1.11' is missing on your system.  You should only need it if
         you modified `acinclude.m4' or `configure.in'.  You might want
         to install the `Automake' and `Perl' packages.  Grab them from
         any GNU archive site.
 cd . && /bin/bash /home/pi/kubohiroya/libpafe/missing --run automake-1.11 --gnu
/home/pi/kubohiroya/libpafe/missing: 行 52: automake-1.11: コマンドが見つかりません
WARNING: `automake-1.11' is missing on your system.  You should only need it if
         you modified `Makefile.am', `acinclude.m4' or `configure.in'.
         You might want to install the `Automake' and `Perl' packages.
         Grab them from any GNU archive site.
CDPATH="${ZSH_VERSION+.}:" && cd . && /bin/bash /home/pi/kubohiroya/libpafe/missing --run autoconf
/home/pi/kubohiroya/libpafe/missing: 行 52: autoconf: コマンドが見つかりません
WARNING: `autoconf' is missing on your system.  You should only need it if
         you modified `configure.in'.  You might want to install the
         `Autoconf' and `GNU m4' packages.  Grab them from any GNU
         archive site.
・
・
・
libtool: link: gcc -I../src -I../ -g -O2 -o .libs/felica_dump felica_dump-felica_dump.o  ../src/.libs/libpafe.dylib
gcc: error: ../src/.libs/libpafe.dylib: No such file or directory
Makefile:282: recipe for target 'felica_dump' failed
make[2]: *** [felica_dump] Error 1
make[2]: Leaving directory '/home/pi/kubohiroya/libpafe/tests'
Makefile:302: recipe for target 'all-recursive' failed
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory '/home/pi/kubohiroya/libpafe'
Makefile:233: recipe for target 'all' failed
make: *** [all] Error 2

エラー出た

pi@raspberrypi:~/kubohiroya/libpafe $ sudo apt-get install autoconf


として、


$ make clean
$ ./configure
$ make
$ sudo make install

とするとインストールできた。

15
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?