3285浏览
查看: 3285|回复: 1

[进阶] RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)

[复制链接]
本帖最后由 IsoFace爱招飞 于 2022-8-26 14:51 编辑

  近期入手了一套 RC522 的 RFID 模块,现在把我近期把玩这个模块的经历分享给大家,大家可以参照这个过程,上手这个模块的玩法。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图1

  刚到手的 RFID 模块与排针是分开的,需要自己焊上,焊什么的不用多说了吧,来这看贴文的应该都要会这个技能。选择焊直排针还是弧排针就看自己的需求了。

  RC522 RFID 的板子大概都是下面这个样子的。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图2

  翻出吃灰已久的焊枪,一番操作后排针就焊上了,下面是找了一张已经焊好的板子的图。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图3

  焊接的地方有一排引脚说明。我查了相关资料,下面整理出来各引脚的定义说明。

SDA SCK MOSI MISO IRQ GND RST 3.3V
选择设备 时钟信号 主出从入(数据) 主入从出(数据) 中断 接地 置位 电源

  接下来将 RC522 与 Arduino 连接,以下使用 Arduino Uno 进行连接。连接的对照表格示意如下:

MFRC522针脚 Arduino Uno 针脚
3.3V 3.3V
GND GND
RST 9
SDA/SS 10
MOSI 11
MISO 12
SCK 13

  范例需要使用到 MFRC522 的库,我们可以在库管理器中搜索到这个库,安装即可。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图4

  库文件的来源:https://github.com/miguelbalboa/rfid

  安装完成后,在 Arduino IDE 菜单栏中选择 文件 - 示例 -  第三方示例 - MFRC522 - DumpInfo  打开示例。

  将 Arduino 连接至电脑,配置开发板选项为 Arduino Uno



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图5

  端口是否已选择 Arduino Uno 所在的端口。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图6

  点击烧录按钮,将程序烧录至 Arduino 开发板中。

  打开串口监视器,查看运行情况。这个时候可以把卡片放到RFID模块上,看到读取的信息。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图7

  通过上述过程,我们已经调试测试了 MFRC522 模块,接下来还会有更多精彩的使用方法与整合运用方式。后续会在本贴陆续更新,请大家持续关注。

IsoFace爱招飞  见习技师
 楼主|

发表于 2022-8-29 13:52:22


  前面使用了 Arduino 来连接 MFRC522 ,现在我打算用 ESP8266 来试一下,当然我还要加上亿点点内容,再加一块显示屏,再加两个继电器,再加两个 LED 发光二极管指示灯。

  下面我把需要用到的零件列一下,方便制作收集:

序号 零件名称 数量
1 NodeMCU ESP8266 Lua WIFI V3 开发板 1
2 2 路 5V/12V 继电器模块 高低电位触发 1
3 MFRC-522 RC522 RFID IC 卡感应 附白卡 1
4 I2C LCD1602 液晶显示屏 1
5 LED 发光二极管 红色、绿色各 1 个,共计 2 个
6 220Ω 电阻 2
7 蜂鸣器模块 1

  接下来,就可以准备开始玩一波了!

  首先要准备将各零件连接起来,连接完成后的图示就是下面显示的这样子:



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图1

  连接连好了,Arduino 代码在哪里?别急,马上给你。

  在使用以下代码前,请先下载并配置以下的库。

Arduino IDE 安装完成以后,进入首选项(Preferences),找到附加开发板管理器地址(Additional Board Manager URLs),并在其后添加如下信息:http://arduino.esp8266.com/stable/package_esp8266com_index.json



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图2

  保存后重启 Arduino IDE,点击菜单栏 工具 - 开发板 - 开发板管理器,找到 esp8266 安装即可。



RC522 RFID 模块上手,由浅入深,探索一下整合(持续更新)图3

  然后点击 工具-开发板-ESP8266 在目录中选择对应的型号。这样开发板就切换完成了。这个示例使用的代码如下:

//购买之 RFID-522 脚位顺序
//RFID脚位     NodeMCU脚位

//MISO        GPIO12     D6
//SCK         GPIO14     D5
//SS(SDA)     GPIO4      D2  有的会连 D8  配合 LCD 使用改为 D4
//MOSI        GPIO13     D7
//GND
//3.3V
//RST         GPIO5      D1  配合 LCD 使用改为 D3
//接无源蜂鸣器,负极接地,信号接 D8 控制输出 3V 或 0V 控制生成长短哔声
//NodeMCU 开发板偏好设置如下
// http://arduino.esp8266.com/stable/package_esp8266com_index.json
//
//LCD脚位接法  NodeMCU
//GND         GND
//VSS         Vin/5V
//SCL         D1
//SDA         D2

//蜂鸣器发出声音
//需要pitches文件
//https://gitee.com/isoface-iot/Smart/blob/master/demo/iot/s-eq-dem-2209_rfid_mqtt_relay/esp8266_ino/pitches.h
#include "pitches.h"
//创建要发音的音色数组
int Music[25] = {
  NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4,
  NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
  NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5,
  NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5
};

#include <stdio.h>
#include <stdlib.h>
#include <Arduino.h>

// WiFi 声明 ============================================================================================================================
//https://github.com/prampec/IotWebConf/tree/v2.x.x
#include <IotWebConf.h>
const char thingName[] = "Relay";                                              // 开发板当 AP 使用时,所采用的名称,类似 SSID.
const char wifiInitialApPassword[] = "66666666";                               // 开发板当 AP 使用时,连接所需之密码.
// 在运行时使用浏览器修改设置,用户名称为 admin,密码就是上行设置值
#define STRING_LEN 128
#define NUMBER_LEN 32

#define CONFIG_VERSION "Relay01"                                                  // 配置特定密钥。如果更改了配置结构,就需修改该值.

// CONFIG_PIN 重新设置脚位,例如设 D0 脚位,在开发板启动时 与GND 脚位连接,将会重新进行,(用于密码遗忘)
#define CONFIG_PIN D0

// 状态指示器针脚,首先它会亮起(保持低电平),在Wifi连接上它会闪铄,当连接到Wifi时,它将关闭(保持高电平)。
#define STATUS_PIN LED_BUILTIN

// MQTT 声明 ============================================================================================================================
// pubsubclient MQTT 程序库网址  https://github.com/knolleary/pubsubclient
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
char hexStr[NUMBER_LEN];                                                                // 设置 RFID 卡号
char mapic[STRING_LEN];                                                                 // 设置该机 MQTT主题名称
bool wifiready = false;

// 声明回传的方法
void configSaved();
boolean formValidator();
boolean connectAp(const char* apName, const char* password);
void connectWifi(const char* ssid, const char* password);
void charToStringL(const char S[], String &D);                                   //将 char[] 转为 String 自订函数

DNSServer dnsServer;
WebServer server(80);
HTTPUpdateServer httpUpdater;

char mqttServerValue[STRING_LEN];
char mqttUserNameValue[STRING_LEN];
char mqttUserPasswordValue[STRING_LEN];
char mqttTopicValue[STRING_LEN];

char ipAddressValue[STRING_LEN];
char gatewayValue[STRING_LEN];
char netmaskValue[STRING_LEN];

IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, CONFIG_VERSION);
IotWebConfParameter ipAddressParam = IotWebConfParameter("IP位址", "ipAddress", ipAddressValue, STRING_LEN, "text", NULL, "192.168.3.124");
IotWebConfParameter gatewayParam = IotWebConfParameter("网关", "gateway", gatewayValue, STRING_LEN, "text", NULL, "192.168.3.1");
IotWebConfParameter netmaskParam = IotWebConfParameter("子网掩码", "netmask", netmaskValue, STRING_LEN, "text", NULL, "255.255.255.0");
IotWebConfParameter mqttServerParam = IotWebConfParameter("MQTT 服务器-", "mqttServer", mqttServerValue, STRING_LEN);
IotWebConfParameter mqttUserNameParam = IotWebConfParameter("MQTT 用户", "mqttUser", mqttUserNameValue, STRING_LEN);
IotWebConfParameter mqttUserPasswordParam = IotWebConfParameter("MQTT 密码", "mqttPass", mqttUserPasswordValue, STRING_LEN, "password");
IotWebConfParameter mqttTopicParam = IotWebConfParameter("MQTT 主题", "mqttTopic", mqttTopicValue, STRING_LEN);

// 开发板固定 发送的主题 mqttTopicValue/uid    这是RFID卡号
// 开发板固定 接收的主题 mqttTopicValue/inp    自行决定发送之消息 例如 单号 货号 产品名称  可以是中文 (LCD无法显示中文)
// 开发板固定 接收的主题 mqttTopicValue/re01   =1 开启 1 号继电器   =0 关闭 1 号继电器
// 开发板固定 接收的主题 mqttTopicValue/re02   =1 开启 2 号继电器   =0 关闭 2 号继电器

IPAddress ipAddress;
IPAddress gateway;
IPAddress netmask;

// LCD1602 声明 =============================================================================================================================
//https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
#include <Wire.h>
#include <LiquidCrystal_I2C.h>                    //引用I2C库

LiquidCrystal_I2C lcd(0x27, 16, 2);               //设备位址,这里的位址是0x3F,一般是0x20,或者0x27,具体看模块手册

// RFID 读卡机 声明 ===========================================================================================================================
#include <SPI.h>
#include "MFRC522.h"

#define RST_PIN D3                                // RFID 读卡机的重置脚位 RC522 
#define SS_PIN  D4                                //RFID 读卡机的芯片选择脚位 
MFRC522 mfrc522(SS_PIN, RST_PIN);                 // 创建MFRC522物件
//============================================================================================================================================

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Starting up...");
  SPI.begin();                                   // Init SPI bus
  mfrc522.PCD_Init();                            // Init MFRC522
  Serial.println("RFID ready");
  // WiFi 设置 =============================================================================================================================
  iotWebConf.setStatusPin(STATUS_PIN);
  iotWebConf.setConfigPin(CONFIG_PIN);
  iotWebConf.addParameter(&ipAddressParam);
  iotWebConf.addParameter(&gatewayParam);
  iotWebConf.addParameter(&netmaskParam);
  iotWebConf.addParameter(&mqttServerParam);
  iotWebConf.addParameter(&mqttUserNameParam);
  iotWebConf.addParameter(&mqttUserPasswordParam);
  iotWebConf.addParameter(&mqttTopicParam);
  iotWebConf.setConfigSavedCallback(&configSaved);             // 回传副程序 - 保存设置
  iotWebConf.setFormValidator(&formValidator);                 // 回传副程序 - 检查输入参数是否有错
  iotWebConf.setApConnectionHandler(&connectAp);               //启动系统缺省 192.168.4.1 之AP 造成无法引入设置之IP
  iotWebConf.setWifiConnectionHandler(&connectWifi);           // 回传副程序 - 启动 WiFi  副程序名称 connectWifi

  // -- Initializing the configuration.

  boolean validConfig = iotWebConf.init();
  if (!validConfig)   {
    mqttServerValue[0] = '\0';
    mqttUserNameValue[0] = '\0';
    mqttUserPasswordValue[0] = '\0';
  }
  else
  {
    strcpy(mapic, mqttTopicValue);
  }
  // -- 在Web服务器上设置必需的URL处理进程.

  server.on("/", handleRoot);
  server.on("/config", [] { iotWebConf.handleConfig(); });
  server.onNotFound([]() {
    iotWebConf.handleNotFound();
  });

  //设置 MQTT ===================================================================================================
  // client.setServer("192.168.3.125", 1883);
  client.setServer(mqttServerValue, 1883);
  client.setCallback(callback);
  if (client.connected()) {
    char mymapic[STRING_LEN];
    strcpy(mymapic, mapic);          // strcpy(复制目的字符串,来源字符串
    strcat(mymapic, "/#");
    client.subscribe(mymapic);
  }

  //设置蜂鸣器===================================================================================================
  pinMode(D8, OUTPUT);                           //设置 D8 脚位用于控制蜂鸣
  lcd.begin();                                   // 初始化LCD
  lcd.backlight();                               //设置LCD背景等亮

  //设置继电器===================================================================================================
  pinMode(D9, OUTPUT);                        //设置继电器使用 D9 D10 脚位为输出状态
  pinMode(D10, OUTPUT);
  digitalWrite(D9,HIGH);
  digitalWrite(D10,HIGH);
}

void loop() {
  // WiFI 运行 ============================================================================================================================
  iotWebConf.doLoop();                         // -- 尽可能频繁地调用doLoop.

  // MQTT 接收主题(单号) ========================================================================================================================
  client.loop();
  if (client.connected()) {
    // RFID 运行 ============================================================================================================================
    if ( ! mfrc522.PICC_IsNewCardPresent()) {      // 检查是否为新卡
      delay(50);
      return;
    }
    // Select one of the cards
    if ( ! mfrc522.PICC_ReadCardSerial()) {
      delay(50);
      return;
    }
    else
    {
      // Show some details of the PICC (that is: the tag/card)
      Serial.print(F("Card UID:"));
      // 转换卡号以十六进位的字符串显示
      to_hex(mfrc522.uid.uidByte, mfrc522.uid.size);

      // 蜂鸣器响声
      beep(23);     //不可大于 24

      //发送卡号到 MQTT Broker
      char pubmapic[STRING_LEN];
      strcpy (pubmapic, mapic);
      strcat(pubmapic, "/uid");
      Serial.println(pubmapic);

      // MQTT 发送主题(卡号) ========================================================================================================================
      Serial.println("connected");
      Serial.println(pubmapic);
      client.publish(pubmapic, hexStr);
      delay(1500);                             //得暂停 要不然回圈会很快扫卡 读好几次
    }
  }
  else
  {
    if (wifiready) {
      reconnect();
    }
  }

  /**    此段程序取消 因为会紧跟着显示 hello Word 信号数
         将来在发送大量字符串时可以参考使用
    long now = millis();
    if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 50, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
    }
  **/

}

// RFID 副程序 =============================================================================================================================
void beep(int len) {
  for (int i = 0; i < len; i++) {
    tone(D8, Music[i], 100); // 从第8Pin发声,发出100ms的声音
    //delay(200);
  }
}

// -----------------------------------------------------------------------------------------------------------------------------------------------
// 转换卡号以十六进位的字符串显示
void to_hex(byte *buffer, byte buffSize) {
  char* s = &hexStr[0];
  for (byte i = 0; i < buffSize; i++) {
    snprintf(s, 3, "%02x", buffer[i]);
    s += 2;
  }
  hexStr[buffSize * 2] = 0;
  Serial.println(hexStr);
  lcd.clear(); //显示清除
  lcd.setCursor(0, 0);                //设置显示行列数
  lcd.print(F("Card UID:"));          //输出字符到LCD1602上
  lcd.setCursor(0, 1);
  lcd.print(hexStr);

}

// WiFi 副程序 =============================================================================================================================
void handleRoot()                                                      //处理“/”路径的Web请求.
{
  // -- 让IotWebConf测试并处理强制门户请求.
  if (iotWebConf.handleCaptivePortal())
  {
    // -- 主要的需 求已经提供.
    return;
  }
  String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" charset=\"utf-8\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>";
  s += "<title>网络设置作业</title></head><body>您好!";
  s += "<ul>";
  s += "<li>固定 IP 设置: ";
  s += ipAddressValue;
  s += "</ul>";
  s += "请至<a href='config'>设置页面</a> 请修改.";
  s += "</body></html>\n";

  server.send(200, "text/html", s);
}
// -----------------------------------------------------------------------------------------------------------------------------------------------
void configSaved()
{
  Serial.println("设置即将更新.");
}
// -----------------------------------------------------------------------------------------------------------------------------------------------
boolean formValidator()
{
  Serial.println("Validating form.");
  boolean valid = true;

  if (!ipAddress.fromString(server.arg(ipAddressParam.getId())))
  {
    ipAddressParam.errorMessage = "请提供正确的 IP 位址!";
    valid = false;
  }
  if (!netmask.fromString(server.arg(netmaskParam.getId())))
  {
    netmaskParam.errorMessage = "请提供正确的子网掩码!";
    valid = false;
  }
  if (!gateway.fromString(server.arg(gatewayParam.getId())))
  {
    gatewayParam.errorMessage = "请提供正确的网关位址!";
    valid = false;
  }

  return valid;
}
// -----------------------------------------------------------------------------------------------------------------------------------------------
boolean connectAp(const char* apName, const char* password)
{
  // -- 自定义AP设置
  return WiFi.softAP(apName, password, 4);
}
// -----------------------------------------------------------------------------------------------------------------------------------------------
void connectWifi(const char* ssid, const char* password)
{
  ipAddress.fromString(String(ipAddressValue));
  netmask.fromString(String(netmaskValue));
  gateway.fromString(String(gatewayValue));

  if (!WiFi.config(ipAddress, gateway, netmask)) {
    Serial.println("WiFi 连接失败 请检查网络设置");
  }
  wifiready = true;
  Serial.print("ip: ");
  Serial.println(ipAddress);
  Serial.print("gw: ");
  Serial.println(gateway);
  Serial.print("net: ");
  Serial.println(netmask);
  WiFi.begin(ssid, password);
}

// MQTT 副程序 =============================================================================================================================
// 接收主题後调用进程
void callback(char* topic, byte* payload, unsigned int length) {
  String mapre01;                                       // 预定收到的主题1   /re01
  String mapre02;                                       // 预定收到的主题2   /re02
  String mapre03;                                       // 预定收到的主题3   /inp
  String mappu01;                                       // 预定发送的主题3   /UID
  String mapres;                                        // 实际从 MQTT Broker 接收到的主题
  String information;                                   // 实际从 MQTT Broker 接收到的主题内容
  String RelayOpen="1"; 
  //将 char[] 转为 String charToStringL(需转换的 Char数组, 转换後的 String)
  charToStringL(mapic, mapre01);                        //用户自设的主题
  charToStringL(mapic, mapre02);
  charToStringL(mapic, mapre03);
  charToStringL(mapic, mappu01);
  mapre01 = mapre01 + "/re01";                          //1号继电器 的主题 
  mapre02 = mapre02 + "/re02";                          //2号继电器 的主题息 
  mapre03 = mapre03 + "/inp";                           //其他收到的主题
  mappu01 = mappu01 + "/uid";                           //发布的主题
  charToStringL(topic, mapres);                         //实际从 MQTT Broker 接收到的主题
  for (int i = 0; i < length; i++) {                    //读取收到的字符串
    information = information + (char)payload[i];
  }
  if (mapres != mappu01) {                              // 不显示发布的主题
     Serial.print("接收消息 " + mapres + " [");
     Serial.print(information);
     Serial.print("] ");
     lcd.clear();                                       // LCD 显示清除
     lcd.setCursor(0, 0);                               // 设置显示行列数
     lcd.print("Message:"); 
     lcd.setCursor(0, 1);
     beep(15);                                          // 蜂鸣器响声 24
     beep(15);
  }

  if (mapres == mapre01) {
    if (information == RelayOpen) {                
      Serial.println("Open Relay-1");
      lcd.print("Open Relay-1");                         //输出消息到 LCD1602 显示
      digitalWrite(D9,LOW);
    }
    else {
      Serial.println("Close Relay-1");
      lcd.print("Close Relay-1");
      digitalWrite(D9,HIGH);
    }
  }
  if (mapres == mapre02) {                  
    if (information == RelayOpen) {
      Serial.println("Open Relay-2");
      lcd.print("Open Relay-2");
      digitalWrite(D10,LOW);
    }
    else {
      Serial.println("Close Relay-2");
      lcd.print("Close Relay-2");
      digitalWrite(D10,HIGH);
    }
  }
  if (mapres == mapre03) {
    lcd.print(information);
  }
 /**
    digitalWrite(D9,LOW);
    delay(200);
    digitalWrite(D10,LOW);
    delay(2000);
    digitalWrite(D9,HIGH);
    delay(200);
    digitalWrite(D10,HIGH);
    delay(1000);
  **/

  Serial.println();

  // 收到消息的第一个字符 开发板上 LED 亮灯

  if ((char)payload[0] == '1') {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is active low on the ESP-01)
  } else {
    digitalWrite(BUILTIN_LED, HIGH);  // 关闭 LED 灯
  }
}

void reconnect() {
  // 一直循环直到连上 MQTT Broker
  while (!client.connected()) {
    Serial.print("正在连接 MQTT Broker...");
    // 随机生成 client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // 开始进行连接
    lcd.clear();                                         // LCD 显示清除
    lcd.setCursor(0, 0);                                 // 设置显示行列数
    if (client.connect(clientId.c_str(), mqttUserNameValue, mqttUserPasswordValue)) {
      Serial.println("MQTT Broker 已经连接上");
      char mymapic[STRING_LEN];
      strcpy(mymapic, mapic);          // strcpy(复制目的字符串,来源字符串
      strcat(mymapic, "/#");
      client.subscribe(mymapic);

      lcd.print("RFID Ready:");                          // 正确开机 在LCD 显示消息

    } else {
      Serial.print("连接失败, rc=");
      Serial.print(client.state());
      Serial.println("五秒钟之后再连接");
      lcd.print("failed, rc=");                          // 在LCD 显示错误消息
      lcd.print(client.state());                         
      lcd.setCursor(0, 1);
      lcd.print("try again in 5 seconds... ");
      delay(5000);
    }
  }
}

//将 char[] 转为 String 自订函数
void charToStringL(const char S[], String & D)
{
  byte at = 0;
  const char *p = S;
  D = "";

  while (*p++) {
    D.concat(S[at++]);
  }
}

  刷写完成后,重启 ESP8266 ,此时 WiFi 是以 AP 模式运行的,我们用手机或者电脑连接到SSID为 Relay 的无限网络,使用浏览器访问 http://192.168.4.1,打开网页页面,点击设置页面,进入设置的页面程序。根据页面要求设置对应的连接项或者设置项,其中的 MQTT 相关的设置项是为后续的连接作铺垫的,所以测试时,也请先设置好可用的MQTT服务器。设置保存后断开连接,并重启ESP8266。

  在通电后,请先立即移除 ESP8266 针脚 TX RX 的连接,查看 LCD 屏幕的显示情况。等待 LCD 屏幕显示 RFID Ready 后,再将 TX RX 针脚重新接上。这个时候,可以将卡片放置于 MFRC522 的感应区,可以听到蜂鸣器的声音,同时屏幕上会显示刷卡的结果。

  到这里了,你可能会问,这个继电器怎么没用上啊?MQTT 是干什么用的?接下来还会有更新的内容,希望大家能持续关注。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail