数据卡 – 折腾(存档) https://blog.sorz.org 已停止更新和维护。该页面为2018年3月创建的存档,其内容可能已过于陈旧、与现状不符,仅作为历史存档用作参考。 Sun, 14 Jan 2018 11:08:20 +0000 zh-CN hourly 1 https://wordpress.org/?v=4.9.4 利用上网卡转发短信至邮箱,来电提醒 /p/sms2email/ /p/sms2email/#comments Tue, 18 Jun 2013 14:11:04 +0000 http://sorz.org/?p=347689 想法
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 网络电话就架成了~

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

]]>
/p/sms2email/feed/ 14