分类 技术开发 下的文章

一、Modbus Tcp

以下指令均为16进制数。

1.读多个地址。
0000 0000 0006 01 03 0001 0010
0000:事务标识符(2字节) ,没有特别定义;
0000:协议标识符(2字节) ,MODBUS协议的场合为0000H;
0006:长度(2字节),后面所跟的字节长度;
01 :单元标识符(1字节),站号,或者说机器地址;
03:功能(1字节) ,请指定功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
0001:起始数据地址;
0010:读个数。

2.写单个地址。
0000 0000 0006 01 06 00FF 000A
0000:事务标识符(2字节) ,没有特别定义;
0000:协议标识符(2字节) ,MODBUS协议的场合为0000H;
0006:长度(2字节),后面所跟的字节长度;
01 :单元标识符(1字节),站号,或者说机器地址;
06:功能(1字节) ,请指定功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
00FF:写入地址;
000A:写入数据。

3.写多个地址。
0000 0000 000B 01 10 0001 0002 04 000A 000B
0000:事务标识符(2字节) ,没有特别定义;
0000:协议标识符(2字节) ,MODBUS协议的场合为0000H;
000B:长度(2字节),后面所跟的字节长度;
01 :单元标识符(1字节),站号,或者说机器地址;
10:功能(1字节) ,请指定功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
0001:起始数据地址;
0002:写个数;
04:字节数,写个数*2;
000A:写入数据1;
000B:写入数据2。

二、Modbus RTU
以下指令均为16进制数。
1.读多个地址。
01 03 0001 0010 15C6
01 :机器地址;
03:功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
0001:起始数据地址;
0010:读个数;
15C6:校验代码(CRC)。

2.写单个地址。
01 06 00FF 000A 39FD
01 :机器地址;
06:功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
00FF:写入地址;
000A:写入数据;
39FD:校验代码(CRC)。

3.写多个地址。
01 10 0001 0002 04 000A 000B 53A6
01 :单元标识符(1字节),站号,或者说机器地址;
10:功能(1字节) ,请指定功能代码;(功能代码补充:03读多个地址,06写单个地址,10写多个地址)
0001:起始数据地址;
0002:写个数;
04:字节数,写个数*2;
000A:写入数据1;
000B:写入数据2;
53A6:校验代码(CRC)。

在开发中涉及数采部分,部分PLC的元软件地址是用16进制标识的。
例如三菱的 M1F 这样,同步时上位机端保存最好也同样记录一样名字的地址,这里就需要16进制的加减运算。
最简单的计算方式是:

(Convert.ToInt32(saddr, 16) + inc).ToString("x").ToUpper();

拓展开来,要是需要进行一个任意长度的16进制加法呢,找了一下网上也没看到有比较好的操作。
检查梳理一下思路,就是从低位到高位逐位相加,然后有进位的参与后边运算,代码如下

 public string HexAdd(string first, string second) {
    var src = first.ToCharArray();
    var des = second.ToCharArray();
    char[] res;
    if (src.Length < des.Length) {
        res=HexAddByChars(des.Reverse().ToArray(), src.Reverse().ToArray());
    } else {
        res=HexAddByChars(src.Reverse().ToArray(), des.Reverse().ToArray());
    }
    return res != null ? string.Concat(res.Reverse()) : string.Empty ;
}

public string HexAdd(string saddr, int inc) {
    return (Convert.ToInt32(saddr, 16) + inc).ToString("x");
}

private char[] HexAddByChars(char[] a, char[] b) {
    var res = new char[a.Length];
    var padd = 0;
    for (var i =0; i< a.Length; i++) {
        if (i >= b.Length) {
            res[i] = HexAddByChar(a[i], Convert.ToChar(padd.ToString()), out padd);
            padd = 0;
        } else {
            if (padd > 0) { //低位结果有进位运算时
                var sa = HexAdd(a[i].ToString(), padd);
                var re = HexAdd(sa, b[i]);
                if (re.Length > 1) {
                    padd = int.Parse(re.Substring(0, 1));
                    res[i] = Convert.ToChar(re.Substring(1));
                } else {
                    res[i] = Convert.ToChar(re);
                }
            } else {
                res[i] = HexAddByChar(a[i], b[i], out padd);
            }
        }
    }
    return res;
}
private char HexAddByChar(char a, char b, out int padd) {
    padd = 0;
    var tres = b=='\0'?a.ToString(): (Convert.ToInt32(a.ToString(),16) + Convert.ToInt32(b.ToString(),16)).ToString("x");
    if (tres.Length > 1) {
        padd = int.Parse(tres.Substring(0, 1));
    }
    return Convert.ToChar(tres.Length>1?tres.Substring(1):tres);
}

注意:运算需要从低位往高位推进,所以上边操作将16进制字符串分拆成数组后,需要先逆序再操作,操作完再逆序保存回来。

总结微信支付异步通知notify_url的数据逻辑
1、微信的异步通知是XML,需要处理后才能转成json方便使用;
2、验签用到的key是商户后台 pay.weixin.qq.com 里边的apikey;
3、验签时,前边下单时透传到异步url参数里边的所有非标参数,均不能参与验签运算否则验签失败。

上码:

<?php
ini_set('date.timezone', 'Asia/Shanghai');
error_reporting(E_ERROR);

function wlog($str){
    //追加方式记录文件,模式w 为覆盖重写
    $myfile = fopen("/data/log/wxpayrecs.log", "a") or die("Unable to open file!");
    fwrite($myfile,  date('y-m-d H:i:s').$str."\r\n");
    fclose($myfile);
}

$susretstr='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
$failretstr='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[]]></return_msg></xml>';

$inBody = file_get_contents('php://input');     //获取数据流
//样例数据 {"appid":"wx5c48****9f1","bank_type":"BOC_DEBIT","cash_fee":"1","fee_type":"CNY","is_subscribe":"Y","mch_id":"152****81",
//"nonce_str":"349217E0F*****1C8C249","openid":"oNvy-*****0SBLI","out_trade_no":"20210*****111",
//"rd":"704","result_code":"SUCCESS","return_code":"SUCCESS","sign":"5E828DF2933BED7E2B2F3BFF3F944CED","time_end":"20230208110409",
//"total_fee":"1","trade_type":"NATIVE","transaction_id":"420000******5576"}

if(empty($inBody)){     //没有发送数据流,可能是普通点击,直接无视为成功访问
    exit($susretstr);
}
//wlog(' Body:   '.str_replace("\n","",$inBody));     //调试阶段写日志

$xml=simplexml_load_string($inBody,'SimpleXMLElement', LIBXML_NOCDATA);
$result=json_decode(json_encode($xml),true);

if(empty($result["sign"])){   //缺少签名,直接失败
    exit("fail");
}
if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
    $Signature=$result['sign'];
    //验签,todo 前边下单增加的透传参数不能参与验签
    unset($result['sign'],$result['rd']);
    //todo 这里可能需要增加sort排序操作
    $s_src= http_build_query($result).'&key=51930******70D';

    //wlog($s_src);
    $vsign=strtoupper(md5($s_src));

    if($Signature==$vsign){
        wlog(' 支付验签 ok');
        //todo 根据交易id,设置数据库交易未已成功

        $out_trade_no=$result["out_trade_no"];
        exit($susretstr);
    }else{
        wlog(' 验签失败 '.$vsign);
        exit($failretstr);
    }
}else{
    exit($failretstr);
}


一直都觉得Windows的更新服务很烦人,因为一次优化类的软件做了特别的操作修改注册表,导致控制面板--管理工具--服务--Windows 更新---无法启动。

解决办法:

按“win+R”或者点击开始菜单,找到运行,在运行输入框里面输入“regedit”进入注册表,然后在注册表找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wuauserv(我的这个在注册表的最下面)

对目录wuauserv右键选择权限--高级--看到所有者--更改--弹出框选择“高级”--弹出框--立即查找--选择中“administrators”--把完全控制打上对号--确定应用

直接上代码,实现功能:程序通过代码修改了config文件后,自动触发重启。

string txtServerIPs = ConfigurationManager.AppSettings["SeverIp"];
string txtSeverPorts = ConfigurationManager.AppSettings["SeverPort"];

//获取Configuration对象
Configuration config = System.Configuration.ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.AppSettings.Settings["SeverIp"].Value = txtServerIP.Text;
config.AppSettings.Settings["SeverPort"].Value = txtSeverPort.Text;
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");

MessageBox.Show("修改成功,程序将重新启动。");

Application.ExitThread();
Application.Exit();
Application.Restart();