利用上网卡转发短信至邮箱,来电提醒

想法

SMS to Email 效果

SMS to Email 效果

唔,最早的想法是:学校离家较远所以有两张SIM卡,而且由于资费原因只能分别在两地使用,想通过互联网代理,使得可以在一地同时使用两张卡..

最早想到的是使用闲置低端安卓机,后来发现手头闲置的一张华为数据卡(EC1261)可以利用,遂试着捣鼓出一个这玩意..

本文实现:

  • 将所收短信转发至指定 E-mail
  • 挂断来电并回复短信,同时发送 E-mail 提醒

虽然还不能接打电话,但至少接收各种验证短信不再麻烦了呢 🙂

设备

闲置 华为数据卡 EC1261 一张
装 ubuntu 的 Cubieboard 一块

这张上网卡除了上网,收发短信也是可以的:流量提醒就是用短信实现的(所以总是一条条地接收);带了个 2.5mm 耳机插口,应该是用来打电话的,虽然电信给的软件不带此功能。

其他型号的华为上网卡应该类似

安装

Windows: 插入装好驱动就成,华为上网卡用串口和 PC 通讯,插入后设备管理器中可见串口号(COMXX)。

Linux: 以前在 ubuntu 10.10 试过插上就能用。在刷了官网上下的 ubuntu 镜像的 Cubieboard 上,貌似装了 usb_modeswitch 才正常。若自行编译内核,需确保勾选usb_wwanoption模块。会看见类似/dev/ttyUSB0的设备出现。

脚本设计运行于 Linux,依赖 Python 2 和 pySerial,发邮件需装 sendmail 。

apt-get install python-serial sendmail usb-modeswitch

通讯

关于如何“操作”上网卡,这个是关键呢,感谢这篇文章给了我线索,搜索关键词 HUAWEI CDMA Datacard Modem AT Command Interface Specification 即可,PDF 里有详细说明。

根据说明写了段程序,完成标题所述功能:hw_smsd.py
(注意下读写串口可能需要 root 权限)

#!/usr/bin/env python
#encoding: utf-8

# Copyright (C) 2013 @XiErCh
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import time
import serial
import smtplib
from datetime import datetime


LOGFILE_PATH  = 'mail.log'
TTY_DEV_PATH  = '/dev/ttyUSB0'

SENDER_EMAIL  = 'hw-smsd@somehost'
NOTICE_EMAIL  = 'your@email'
CALL_SMSREPLY = u'抱歉,本号码暂时停用。请拨打 13800138000 联系我,谢谢。'

class ATSerial(serial.Serial):
    '''Send a AT command and return the response or raise a error.
    '''
    # Please see HUAWEI CDMA Datacard Modem AT Command Interface Specification
    # for detial.
    def at(self, cmd):
        self.write('AT%s\r\n' % cmd)
        self.flush()
        self.readline()
        rsp = ''
        while True:
            l = self.readline().strip()
            if not l:
                continue
            elif l == 'OK':
                return rsp
            elif l == 'ERROR':
                raise IOError('AT: ERROR')
            elif l == 'COMMAND NOT SUPPORT':
                raise IOError('AT: COMMAND NOT SUPPORT')
            elif l == 'TOO MANY PARAMETERS':
                raise IOError('AT: TOO MANY PARAMETERS')
            rsp += '%s\n' % l


def get_serial():
    return ATSerial(TTY_DEV_PATH)


def sendmail(msg, subject='',fromaddr=SENDER_EMAIL, toaddr=NOTICE_EMAIL):
    s = smtplib.SMTP('localhost')
    msg = u"From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s" \
            % (fromaddr, toaddr, subject, msg)
    s.sendmail(fromaddr, toaddr, msg.encode('utf-8'))
    logfile.write(msg.encode('utf-8') + '\n\n')
    logfile.flush()
    s.quit()


def sendsms(s, number, text, number_type=0):
    logfile.write('SMS to %s:\n%s\n\n' % (number, text))
    logfile.flush()
    s.write('AT^HCMGS="%s",%s\r' % (number, number_type))
    s.flush()
    time.sleep(0.1)
    s.write(text.encode('utf-16-be') + '\x00\x1a')
    while True:
        l = s.readline().strip()
        if l == 'OK':
            return
        elif l.startswith('+CMS ERROR:'):
            raise IOError(l)


def e_new_sms(s, opt):
    # opt[0]     callerID
    #    [1:7]   yyyy,mm,dd,hh,mm,ss
    #    [7]     lang
    #    [8]     format
    #    [9]     length
    #    [10-13] ...
    text = s.read(int(opt[9])).decode('utf-16-be')
    s.read(3) # Ignore ^Z\r\n
    send_time = "%s-%s-%s %s:%s:%s" % tuple(opt[1:7])
    recv_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    mailsubject = u'来自 %s 的短信' % opt[0]
    mailtext = u'%s\r\n\r\n----\r\n发送号码:%s\r\n发送日期:%s\r\n接收日期:%s' \
            % (text, opt[0], send_time, recv_time)
    sendmail(mailtext, mailsubject)
    print("SMS => E-mail")
    s.at('+CNMA') # ack


def e_income_call(s, opt):
    # opt[0] number
    #    [1] type
    #    [5] CLI validity
    if not opt[0]:
        if opt[5] == '1':
            opt[0] = u'隐藏号码'
        else:
            opt[0] = u'未知号码'
    mailsubject = u'来自 %s 的来电' % opt[0]
    mailtext = u'来电者:%s\r\n来电时间:%s' \
            % (opt[0], datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
    sendmail(mailtext, mailsubject)
    print("Call => E-mail")
    s.at('+CHV') # reject
    if opt[0].startswith('1'):
        sendsms(s, opt[0], CALL_SMSREPLY, opt[1])
        print("Call <= SMS")


def loop(s):
    line = s.readline().strip()
    if not line:
        return
    elif line == 'RING':
        return
    try:
        cmd, rsp = line.split(':', 1)
        rsp = rsp.strip()
        opt = rsp.split(',')
    except ValueError:
        print('unknow [%s]' % line)
        return

    if cmd == '^HCMT':
        e_new_sms(s, opt)
    elif cmd == '+CLIP':
        e_income_call(s, opt)

    else:
        print('unknow - %s:' % cmd)
        print(opt)


def main():
    s = get_serial()

    # SMS Parameter Selection:
    # ^HSMSSS=<ack>,<prt>,<fmt>,<prv>
    #   <fm>  6: Unicode
    s.at("^HSMSSS=0,0,6,0")

    # New SMS Notification Setting:
    # +CNMI=[<mode>[,<mt>[,<bm>[,<ds>[,<bfr>]]]]]
    s.at("+CNMI=1,2")

    #sendmail('test', 'sendmail test')
    while True:
        loop(s)

if __name__ == '__main__':
    logfile = open(LOGFILE_PATH, 'a')
    main()

还比较简陋,但运行了几天没问题,就发上来~怎么说也算留个思路吧。

改进

记得刚才说的,这张上网卡有带一个 2.5mm 插口么,
和 Cubieboard 的 Line-In, 耳机口 相连,再装个 Asterisk 配置一下,一个自己的 SIP 网络电话就架成了~

不过目前来说,我对这功能需求并不迫切呢..

利用上网卡转发短信至邮箱,来电提醒》上有14条评论

  1. 依云

    哇好高级,直接发 AT 指令耶~我是用 gnokii 收短信的,存成 mbox 然后拿 mutt 读……
    不知道你能取得当前网络信息(2G 还是 3G)不?我找了好久,只能取得信号强度……

    回复
    1. Null 文章作者

      都是自己瞎折腾,原来有现成的软件可以读啊… 乃知道好多好东西啊…
      华为那个 PDF 里没写呢.. 看电信 Windows 客户端上 2G/3G 信号分别写,应该能拿到的才是..
      如果是说上网的话.. 我还不知道网络数据要从哪,怎么走呢..

      回复
        1. Null 文章作者

          现在设备不在身边没法试呢..
          “当前网络信息(2G 还是 3G)” 是单指上网么..
          看它拨号软件的界面,应该是连接时指定的… 设备自己应该不会自动切换吧..

        2. 依云

          是啊。手机上网的话会显示当前使用的网络类型。如果设备自己不切换的话,那如何确定设备它要连哪个呢?难道有默认值?

  2. 依云

    换了个卡,gnokii 一开始还能收发短信的,现在收不到短信了……
    ttyUSB0 在上网时打不开,说资源忙。ttyUSB1 不理我,ttyUSB2 一直在报告流量和信号强度之类的信息,但是各种命令不支持……

    回复
    1. Null 文章作者

      机器前两天被我升 14.04 的时候搞挂了.. 暑假之前没法试了.. (orz
      上网时 PPP 占掉了一个,按理还有一个能收发 AT 指令的吧..

      回复
      1. Null 文章作者

        所以收不到短信了么……
        记得收短信有两个模式..
        一种是收到后存卡上,然后马上向网络确认已接收,要 PC 读卡上的短信..
        一种是收到直接发给 PC,要 PC 这边确认接收后,卡再向网络确认已接收..

        回复
        1. 依云

          看来 gnokii 是第一种。然后我发现一堆不请自来的 ^DSFLOWRPT 以及少量 ^BOOT 和更少的 ^RSSI 出来捣乱,后来终于找到一个 ^CURC=0 指令让它禁声了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注