查看: 3643|回复: 0

[转]php实现短信猫短信发送php_serial.class.php串口类

[复制链接]
发表于 2015-11-16 15:55:32 | 显示全部楼层 |阅读模式

       MSN和飞信报警用了两三个月了,总结如下:想收到报警消息,139的邮箱是肯定能收到的,有邮件必发短信,但是中间肯定会消耗一些时间;飞信次之十次会有九次收到吧,当然了还是比较稳定的,也比139的短信早到;MSN的话,感觉并不是每次都能收到,难道是不在线的时候发了信息上线以后就没有留言?但是有个缺点就是,这三个报警都是依赖别人的服务器,如果人家碰巧出问题了,咱也跟着吃亏?思来想去,觉得最靠谱的还是有自己的短信发送设备。
       正好手上有个GSM的modem。因为最近正迷php,所以不打算用其它的二次开发包之类的东西了。说干就干,晚上一回到家就开始找资料。这一查不要紧,你还别说,还真有用php干这事儿的。但是网上传来传去的都是那一篇通过串口+GSM MODEM发送短信示例。这个示例是windows平台下的,还用到了一个dll文件。我现在家里用的系统也换成ubuntu了,再说以后报警的服务器也是linux,总不能为了发个短信再专门换成windows系统吧。于是决定自己动手,用php的函数写linux下的程序,由此埋下了祸根。
       功夫不负有心人啊,很快在国外的一个网站找到了一个php操作串口的类:php_serial。class。于是开始动手用minicom调试modem。因为在以前单位的时候,帮别人用超级终端调试过modem,所以命令还记得一些,很快,用at指令通过minicom发送短信成功(还好俺有两个手机,要不做个实验都没法做)。ok,改用php操作modem,用at指令发英文短信,成功!good。没想到这么顺利。取得了点小胜利的我不满足只发送英文短信,于是开始尝试发送中文短信。但是直接通过at指令输入中文,modem不买帐。收到的是空白短信。这可如何是好?于是开始细细研究网上那篇通过串口+GSM MODEM发送短信示例,于是知道了,想发送中文短信就得用pdu格式,于是又开始研究pdu格式怎么回事儿。一直到睡觉的点,也没从我的modem里发出中文来。不行,先睡觉。
      一觉醒来,又细细看了一遍pdu的格式,并一遍一遍的细细学习通过串口+GSM MODEM发送短信示例的生成方法。经过一遍一遍的尝试终于可以收到一些乱码的中文了,又经过尝试终于收到了和发送字数相同的小方块的短信了。没办法到上班的点了,于是赶紧到单位。间隙的时候,问以前单位负责开始短信群发软件的同事小杨怎么用pdu格式发送短信。小杨告诉了我,要用到的at指令,以及pdu格式的生成方法,虽然没有帮我解决问题但是至少肯定了:1,我的操作方法是对的,因为可以成功收到短信;2,收到字数相等的方块,应该是编码的时候没处理好。晚上回到家又一遍一遍的看之前那篇文章,很不理解,为啥用同样的函数生成的字符串,最后的差距咋就那么大呢?边尝试边找资料,不经意间在网上我发现一篇文章说发送短信"晚上好123"的编码是:"665A4E0A597D003100320033"。我把这个串直接放到我程序里,一发送,嘿,你猜怎么着?还真收到了"晚上好123"。有点意思!于是,我也用我的函数生成"晚上好123",结果我发现了一个规律!
对的:665A4E0A597D003100320033
我的:5A660A4E7D59310032003300
      每四个字符里两两错位!!!发现这个规律以后,就简单了,因为这样就确定确实是编码那块的原因了。但是编码那块:iconv,ord,substr,sprintf好几个系统函数,一个一个熟悉,改来改去都不行,这时候已经晚上十一点半了(我习惯是十点半睡,七八点起,标准的小学生作息时间)。但是离终点这么近了,真不甘心放弃!初中时候养成的好习惯,今日事,今日毕,因为明天还有明天的事!洗把脸,重新整理一下思路:虽然大体看明白了pdu格式,但是字符串转换这块,我还真理解不透。怎么办呢?看来从根上解决这个问题,我功力是不够了。往前不行,咱可以迂回嘛。那我就自己写一个函数把这个字符串的顺序变回来!你还真别说,这招好使!而且再发其它的中文短信,也可以正常收到啦!!!呵呵,看来今晚注定好梦了。
虽然凑合能用了,但是还不知道是什么原因啊。自己搞不定不要紧,公司里我周围坐着的可全是php高手。午休的时候,把会军叫过来,说明事情的来来回回,会军看了一遍代码以后,断定是字符的高低字节问题!因为linux系统和windows系统,对unicode处理的时候,正好是高低位相反的!说实话,当时真惊呆了,我咋就没考虑到是操作系统问题呢!知道了问题的原因以后,我和会军开始分别想从源头解决这个问题。但是半个小时过去了,发现要实现这个太难了,于是我提议放弃,就用我写的这个函数凑合着用。但是还是会军老道。虽然放弃了这步,但是最终还是通过修改转码那块的顺序搞定了这个问题!!!
      通过这次的编码训练又使我学习了很多东西,关于php,关于解决问题的思路。因为用了开源的php类,也参考了网上其它的同类文章,本着开源精神,现将全部代码整理如下:

  1. <?php

  2. include "php_serial.class.php";

  3. //加载php操作串口的类
  4. $serial = new phpSerial;

  5. //连接USB gas modem
  6. $serial->deviceSet("/dev/ttyUSB0");
  7. $serial->deviceOpen();

  8. //要发送的手机号:1531170xxxx
  9. $phone_sendto = InvertNumbers('861531170xxxx');
  10. //要发送的短信:I am rainbird,i love 中国
  11. $message = hex2str('I am rainbird,i love 中国');
  12. $mess = "11000D91".$phone_sendto."000800".sprintf("%02X",strlen($message)/2).$message;
  13. $serial->sendMessage("at+cmgf=0".chr(13));
  14. $serial->sendMessage("at+cmgs=".sprintf("%d",strlen($mess)/2).chr(13));
  15. //不加短信中心号码
  16. $serial->sendMessage('00'.$mess.chr(26));
  17. //加短信中心号码
  18. //$phone_center = InvertNumbers('8613800100500');
  19. //$mess_ll = "0891".$phone_center.$mess;
  20. //$serial->sendMessage($mess_ll.chr(26));

  21. //用完了就关掉,有始有终好习惯
  22. $serial->deviceClose();

  23. //将utf8的短信转成ucs2格式
  24. function hex2str($str) {
  25.         $hexstring=iconv("UTF-8", "UCS-2", $str);
  26.         $str = '';
  27.         for($i=0; $i<strlen($hexstring)/2; $i++){
  28.                 $str .= sprintf("%02X",ord(substr($hexstring,$i*2+1,1)));
  29.                 $str .= sprintf("%02X",ord(substr($hexstring,$i*2,1)));
  30.         }
  31.         return $str;
  32. }
  33. //手机号翻转,pdu格式要求
  34. function InvertNumbers($msisdn) {
  35.         $len = strlen($msisdn);
  36.         if ( 0 != fmod($len, 2) ) {
  37.                 $msisdn .= "F";
  38.                 $len = $len + 1;
  39.         }

  40.         for ($i=0; $i<$len; $i+=2) {
  41.                 $t = $msisdn[$i];
  42.                 $msisdn[$i] = $msisdn[$i+1];
  43.                 $msisdn[$i+1] = $t;
  44.         }
  45.         return $msisdn;
  46. }
  47. ?>
复制代码

其实如果只需要发送英文短信的话,只需要用php_serial。class连接modem并用at指令发短信就可以了。发送中文的时候才会用到pdu格式,当然用了pdu格式以后可以发混合的中英文。本文只作测试没有考虑短信最多只能发送74个汉字的情况,如需要请自行处理。PHP串口操作类php_serial
  1. <?php
  2. define ("SERIAL_DEVICE_NOTSET", 0);
  3. define ("SERIAL_DEVICE_SET", 1);
  4. define ("SERIAL_DEVICE_OPENED", 2);

  5. class phpSerial
  6. {
  7.         var $_device = null;
  8.         var $_windevice = null;
  9.         var $_dHandle = null;
  10.         var $_dState = SERIAL_DEVICE_NOTSET;
  11.         var $_buffer = "";
  12.         var $_os = "";

  13.         var $autoflush = true;

  14.         function phpSerial ()
  15.         {
  16.                 setlocale(LC_ALL, "en_US");

  17.                 $sysname = php_uname();

  18.                 if (substr($sysname, 0, 5) === "Linux")
  19.                 {
  20.                         $this->_os = "linux";

  21.                         if($this->_exec("stty --version") === 0)
  22.                         {
  23.                                 register_shutdown_function(array($this, "deviceClose"));
  24.                         }
  25.                         else
  26.                         {
  27.                                 trigger_error("No stty availible, unable to run.", E_USER_ERROR);
  28.                         }
  29.                 }
  30.                 elseif(substr($sysname, 0, 7) === "Windows")
  31.                 {
  32.                         $this->_os = "windows";
  33.                         register_shutdown_function(array($this, "deviceClose"));
  34.                 }
  35.                 else
  36.                 {
  37.                         trigger_error("Host OS is neither linux nor windows, unable tu run.", E_USER_ERROR);
  38.                         exit();
  39.                 }
  40.         }

  41.         function deviceSet ($device)
  42.         {
  43.                 if ($this->_dState !== SERIAL_DEVICE_OPENED)
  44.                 {
  45.                         if ($this->_os === "linux")
  46.                         {
  47.                                 if (preg_match("@^COM(\d+):?$@i", $device, $matches))
  48.                                 {
  49.                                         $device = "/dev/ttyS" . ($matches[1] - 1);
  50.                                 }

  51.                                 if ($this->_exec("stty -F " . $device) === 0)
  52.                                 {
  53.                                         $this->_device = $device;
  54.                                         $this->_dState = SERIAL_DEVICE_SET;
  55.                                         return true;
  56.                                 }
  57.                         }
  58.                         elseif ($this->_os === "windows")
  59.                         {
  60.                                 if (preg_match("@^COM(\d+):?$@i", $device, $matches) and $this->_exec(exec("mode " . $device)) === 0)
  61.                                 {
  62.                                         $this->_windevice = "COM" . $matches[1];
  63.                                         $this->_device = "\\.\com" . $matches[1];
  64.                                         $this->_dState = SERIAL_DEVICE_SET;
  65.                                         return true;
  66.                                 }
  67.                         }

  68.                         trigger_error("Specified serial port is not valid", E_USER_WARNING);
  69.                         return false;
  70.                 }
  71.                 else
  72.                 {
  73.                         trigger_error("You must close your device before to set an other one", E_USER_WARNING);
  74.                         return false;
  75.                 }
  76.         }

  77.         function deviceOpen ($mode = "r+b")
  78.         {
  79.                 if ($this->_dState === SERIAL_DEVICE_OPENED)
  80.                 {
  81.                         trigger_error("The device is already opened", E_USER_NOTICE);
  82.                         return true;
  83.                 }

  84.                 if ($this->_dState === SERIAL_DEVICE_NOTSET)
  85.                 {
  86.                         trigger_error("The device must be set before to be open", E_USER_WARNING);
  87.                         return false;
  88.                 }

  89.                 if (!preg_match("@^[raw]\+?b?$@", $mode))
  90.                 {
  91.                         trigger_error("Invalid opening mode : ".$mode.". Use fopen() modes.", E_USER_WARNING);
  92.                         return false;
  93.                 }

  94.                 $this->_dHandle = @fopen($this->_device, $mode);

  95.                 if ($this->_dHandle !== false)
  96.                 {
  97.                         stream_set_blocking($this->_dHandle, 0);
  98.                         $this->_dState = SERIAL_DEVICE_OPENED;
  99.                         return true;
  100.                 }

  101.                 $this->_dHandle = null;
  102.                 trigger_error("Unable to open the device", E_USER_WARNING);
  103.                 return false;
  104.         }

  105.         function deviceClose ()
  106.         {
  107.                 if ($this->_dState !== SERIAL_DEVICE_OPENED)
  108.                 {
  109.                         return true;
  110.                 }

  111.                 if (fclose($this->_dHandle))
  112.                 {
  113.                         $this->_dHandle = null;
  114.                         $this->_dState = SERIAL_DEVICE_SET;
  115.                         return true;
  116.                 }

  117.                 trigger_error("Unable to close the device", E_USER_ERROR);
  118.                 return false;
  119.         }

  120.         function confBaudRate ($rate)
  121.         {
  122.                 if ($this->_dState !== SERIAL_DEVICE_SET)
  123.                 {
  124.                         trigger_error("Unable to set the baud rate : the device is either not set or opened", E_USER_WARNING);
  125.                         return false;
  126.                 }

  127.                 $validBauds = array (
  128.                         110    => 11,
  129.                         150    => 15,
  130.                         300    => 30,
  131.                         600    => 60,
  132.                         1200   => 12,
  133.                         2400   => 24,
  134.                         4800   => 48,
  135.                         9600   => 96,
  136.                         19200  => 19,
  137.                         38400  => 38400,
  138.                         57600  => 57600,
  139.                         115200 => 115200
  140.                 );

  141.                 if (isset($validBauds[$rate]))
  142.                 {
  143.                         if ($this->_os === "linux")
  144.                         {
  145.                                 $ret = $this->_exec("stty -F " . $this->_device . " " . (int) $rate, $out);
  146.                         }
  147.                         elseif ($this->_os === "windows")
  148.                         {
  149.                                 $ret = $this->_exec("mode " . $this->_windevice . " BAUD=" . $validBauds[$rate], $out);
  150.                         }
  151.                         else return false;

  152.                         if ($ret !== 0)
  153.                         {
  154.                                 trigger_error ("Unable to set baud rate: " . $out[1], E_USER_WARNING);
  155.                                 return false;
  156.                         }
  157.                 }
  158.         }

  159.         function confParity ($parity)
  160.         {
  161.                 if ($this->_dState !== SERIAL_DEVICE_SET)
  162.                 {
  163.                         trigger_error("Unable to set parity : the device is either not set or opened", E_USER_WARNING);
  164.                         return false;
  165.                 }

  166.                 $args = array(
  167.                         "none" => "-parenb",
  168.                         "odd"  => "parenb parodd",
  169.                         "even" => "parenb -parodd",
  170.                 );

  171.                 if (!isset($args[$parity]))
  172.                 {
  173.                         trigger_error("Parity mode not supported", E_USER_WARNING);
  174.                         return false;
  175.                 }

  176.                 if ($this->_os === "linux")
  177.                 {
  178.                         $ret = $this->_exec("stty -F " . $this->_device . " " . $args[$parity], $out);
  179.                 }
  180.                 else
  181.                 {
  182.                         $ret = $this->_exec("mode " . $this->_windevice . " PARITY=" . $parity{0}, $out);
  183.                 }

  184.                 if ($ret === 0)
  185.                 {
  186.                         return true;
  187.                 }

  188.                 trigger_error("Unable to set parity : " . $out[1], E_USER_WARNING);
  189.                 return false;
  190.         }

  191.         function confCharacterLength ($int)
  192.         {
  193.                 if ($this->_dState !== SERIAL_DEVICE_SET)
  194.                 {
  195.                         trigger_error("Unable to set length of a character : the device is either not set or opened", E_USER_WARNING);
  196.                         return false;
  197.                 }

  198.                 $int = (int) $int;
  199.                 if ($int < 5) $int = 5;
  200.                 elseif ($int > 8) $int = 8;

  201.                 if ($this->_os === "linux")
  202.                 {
  203.                         $ret = $this->_exec("stty -F " . $this->_device . " cs" . $int, $out);
  204.                 }
  205.                 else
  206.                 {
  207.                         $ret = $this->_exec("mode " . $this->_windevice . " DATA=" . $int, $out);
  208.                 }

  209.                 if ($ret === 0)
  210.                 {
  211.                         return true;
  212.                 }

  213.                 trigger_error("Unable to set character length : " .$out[1], E_USER_WARNING);
  214.                 return false;
  215.         }

  216.         function confStopBits ($length)
  217.         {
  218.                 if ($this->_dState !== SERIAL_DEVICE_SET)
  219.                 {
  220.                         trigger_error("Unable to set the length of a stop bit : the device is either not set or opened", E_USER_WARNING);
  221.                         return false;
  222.                 }

  223.                 if ($length != 1 and $length != 2 and $length != 1.5 and !($length == 1.5 and $this->_os === "linux"))
  224.                 {
  225.                         trigger_error("Specified stop bit length is invalid", E_USER_WARNING);
  226.                         return false;
  227.                 }

  228.                 if ($this->_os === "linux")
  229.                 {
  230.                         $ret = $this->_exec("stty -F " . $this->_device . " " . (($length == 1) ? "-" : "") . "cstopb", $out);
  231.                 }
  232.                 else
  233.                 {
  234.                         $ret = $this->_exec("mode " . $this->_windevice . " STOP=" . $length, $out);
  235.                 }

  236.                 if ($ret === 0)
  237.                 {
  238.                         return true;
  239.                 }

  240.                 trigger_error("Unable to set stop bit length : " . $out[1], E_USER_WARNING);
  241.                 return false;
  242.         }

  243.         function confFlowControl ($mode)
  244.         {
  245.                 if ($this->_dState !== SERIAL_DEVICE_SET)
  246.                 {
  247.                         trigger_error("Unable to set flow control mode : the device is either not set or opened", E_USER_WARNING);
  248.                         return false;
  249.                 }

  250.                 $linuxModes = array(
  251.                         "none"     => "clocal -crtscts -ixon -ixoff",
  252.                         "rts/cts"  => "-clocal crtscts -ixon -ixoff",
  253.                         "xon/xoff" => "-clocal -crtscts ixon ixoff"
  254.                 );
  255.                 $windowsModes = array(
  256.                         "none"     => "xon=off octs=off rts=on",
  257.                         "rts/cts"  => "xon=off octs=on rts=hs",
  258.                         "xon/xoff" => "xon=on octs=off rts=on",
  259.                 );

  260.                 if ($mode !== "none" and $mode !== "rts/cts" and $mode !== "xon/xoff") {
  261.                         trigger_error("Invalid flow control mode specified", E_USER_ERROR);
  262.                         return false;
  263.                 }

  264.                 if ($this->_os === "linux")
  265.                         $ret = $this->_exec("stty -F " . $this->_device . " " . $linuxModes[$mode], $out);
  266.                 else
  267.                         $ret = $this->_exec("mode " . $this->_windevice . " " . $windowsModes[$mode], $out);

  268.                 if ($ret === 0) return true;
  269.                 else {
  270.                         trigger_error("Unable to set flow control : " . $out[1], E_USER_ERROR);
  271.                         return false;
  272.                 }
  273.         }

  274.         function setSetserialFlag ($param, $arg = "")
  275.         {
  276.                 if (!$this->_ckOpened()) return false;

  277.                 $return = exec ("setserial " . $this->_device . " " . $param . " " . $arg . " 2>&1");

  278.                 if ($return{0} === "I")
  279.                 {
  280.                         trigger_error("setserial: Invalid flag", E_USER_WARNING);
  281.                         return false;
  282.                 }
  283.                 elseif ($return{0} === "/")
  284.                 {
  285.                         trigger_error("setserial: Error with device file", E_USER_WARNING);
  286.                         return false;
  287.                 }
  288.                 else
  289.                 {
  290.                         return true;
  291.                 }
  292.         }

  293.         function sendMessage ($str, $waitForReply = 0.1)
  294.         {
  295.                 $this->_buffer .= $str;

  296.                 if ($this->autoflush === true) $this->flush();

  297.                 usleep((int) ($waitForReply * 1000000));
  298.         }

  299.         function readPort ($count = 0)
  300.         {
  301.                 if ($this->_dState !== SERIAL_DEVICE_OPENED)
  302.                 {
  303.                         trigger_error("Device must be opened to read it", E_USER_WARNING);
  304.                         return false;
  305.                 }

  306.                 if ($this->_os === "linux")
  307.                 {
  308.                         $content = ""; $i = 0;

  309.                         if ($count !== 0)
  310.                         {
  311.                                 do {
  312.                                         if ($i > $count) $content .= fread($this->_dHandle, ($count - $i));
  313.                                         else $content .= fread($this->_dHandle, 128);
  314.                                 } while (($i += 128) === strlen($content));
  315.                         }
  316.                         else
  317.                         {
  318.                                 do {
  319.                                         $content .= fread($this->_dHandle, 128);
  320.                                 } while (($i += 128) === strlen($content));
  321.                         }

  322.                         return $content;
  323.                 }
  324.                 elseif ($this->_os === "windows")
  325.                 {
  326.                         /* Do nohting : not implented yet */
  327.                 }

  328.                 trigger_error("Reading serial port is not implemented for Windows", E_USER_WARNING);
  329.                 return false;
  330.         }

  331.         function flush ()
  332.         {
  333.                 if (!$this->_ckOpened()) return false;

  334.                 if (fwrite($this->_dHandle, $this->_buffer) !== false)
  335.                 {
  336.                         $this->_buffer = "";
  337.                         return true;
  338.                 }
  339.                 else
  340.                 {
  341.                         $this->_buffer = "";
  342.                         trigger_error("Error while sending message", E_USER_WARNING);
  343.                         return false;
  344.                 }
  345.         }


  346.         function _ckOpened()
  347.         {
  348.                 if ($this->_dState !== SERIAL_DEVICE_OPENED)
  349.                 {
  350.                         trigger_error("Device must be opened", E_USER_WARNING);
  351.                         return false;
  352.                 }

  353.                 return true;
  354.         }

  355.         function _ckClosed()
  356.         {
  357.                 if ($this->_dState !== SERIAL_DEVICE_CLOSED)
  358.                 {
  359.                         trigger_error("Device must be closed", E_USER_WARNING);
  360.                         return false;
  361.                 }

  362.                 return true;
  363.         }

  364.         function _exec($cmd, &$out = null)
  365.         {
  366.                 $desc = array(
  367.                         1 => array("pipe", "w"),
  368.                         2 => array("pipe", "w")
  369.                 );

  370.                 $proc = proc_open($cmd, $desc, $pipes);

  371.                 $ret = stream_get_contents($pipes[1]);
  372.                 $err = stream_get_contents($pipes[2]);

  373.                 fclose($pipes[1]);
  374.                 fclose($pipes[2]);

  375.                 $retVal = proc_close($proc);

  376.                 if (func_num_args() == 2) $out = array($ret, $err);
  377.                 return $retVal;
  378.         }

  379. }
  380. ?>
复制代码


本文转自雷田博客:http://www.23to.com/technical/37.html
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|小黑屋|未来时代科技 ( 粤ICP备12044031号-1

GMT+8, 2020-8-12 10:48 , Processed in 0.174392 second(s), 28 queries .

Powered by WLSD X3.1

© 2013-2014 WLSD Inc.

快速回复 返回顶部 返回列表
 
【电话】(15118131494)
【QQ】 未来时代科技01 售前咨询
【QQ】 未来时代科技02 售后技术
【旺旺】 请问有什么可以帮到您?不在线可留言.
【邮箱】
inextera@sina.com
【地址】 (深圳市龙岗坂田扬马小区)