diff --git a/README_ZH.md b/README_ZH.md
new file mode 100644
index 0000000..673f3c9
--- /dev/null
+++ b/README_ZH.md
@@ -0,0 +1,32 @@
+# QuecPython-TR069-CWMP
+
+中文| [English](./readme.md)
+
+## 概述
+
+TR-069,全称为"Technical Report 069",是由Broadband Forum(原DSL Forum)制定的一种应用层协议,用于远程管理和配置家庭和商业用户的宽带设备,如家庭网关、路由器和VoIP电话。该协议也称为CPE WAN Management Protocol(CWMP),它使得互联网服务提供商(ISP)能够从中心服务器上自动配置、监控和管理连接到网络的终端设备。
+
+**Quecpython**版本的**TR069**目前主要结合了**CWMP**和**RPC**可定制的功能, 以满足客户需求, 客户只需要注册对应的时间即可处理对应的**RPC**事件来参与**ACS**与**CPE**之间的业务交互。
+
+## 用法
+
+- [TR069使用说明](./docs/zh/TR069使用说明.md)
+- [示例代码](./code/main.py)
+
+## 贡献
+
+我们欢迎对本项目的改进做出贡献!请按照以下步骤进行贡献:
+
+1. Fork 此仓库。
+2. 创建一个新分支(`git checkout -b feature/your-feature`)。
+3. 提交您的更改(`git commit -m 'Add your feature'`)。
+4. 推送到分支(`git push origin feature/your-feature`)。
+5. 打开一个 Pull Request。
+
+## 许可证
+
+本项目使用 Apache 许可证。详细信息请参阅 [LICENSE](./LICENSE) 文件。
+
+## 支持
+
+如果您有任何问题或需要支持,请参阅 [QuecPython 文档](https://python.quectel.com/doc) 或在本仓库中打开一个 issue。
diff --git a/code/simulation.py b/code/main.py
similarity index 92%
rename from code/simulation.py
rename to code/main.py
index eae1f20..35865bf 100644
--- a/code/simulation.py
+++ b/code/main.py
@@ -1,392 +1,387 @@
-# Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import _thread
-import usys
-import osTimer
-import ubinascii
-import urandom as random
-from usr import urequest
-# import request
-from usr import methods
-from usr import xmlUtils
-import ujson as json
-from usr.tcp_server import TcpServer
-
-NAMESPACES = {
- "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
- "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
- "xsd": "http://www.w3.org/2001/XMLSchema",
- "xsi": "http://www.w3.org/2001/XMLSchema-instance",
- "cwmp": "urn:dslforum-org:cwmp-1-0"
-}
-
-nextInformTimeout = osTimer()
-pendingInform = False
-http = None
-requestOptions = None
-control_cwmp = None
-device = None
-httpAgent = None
-basicAuth = None
-cookie = None
-first = True
-tr069_server = None
-
-
-def createSoapDocument(id, body):
- headerNode = xmlUtils.node(
- "soap-env:Header",
- {},
- xmlUtils.node(
- "cwmp:ID",
- {"soap-env:mustUnderstand": 1},
- id
- )
- )
-
- bodyNode = xmlUtils.node("soap-env:Body", {}, body)
- namespaces = {}
- for prefix in NAMESPACES:
- namespaces["xmlns:{}".format(prefix)] = NAMESPACES[prefix]
-
- env = xmlUtils.node("soap-env:Envelope", namespaces, [headerNode, bodyNode])
-
- return "\n{}".format(env)
-
-
-def sendRequest(xml, callback):
- global httpAgent, cookie
- headers = {}
- body = xml or ""
-
- headers["Content-Type"] = "text/xml; charset=\"utf-8\""
- # TODO 暂时不用
- headers["Accept-Encoding"] = "gzip, deflate"
- headers["Accept"] = "*/*"
- headers["Authorization"] = basicAuth
-
- if cookie:
- headers["Cookie"] = cookie
-
- options = {
- "method": "POST",
- "headers": headers,
- }
- options.update(requestOptions)
- print("------------------ headers & body ------------------")
- # print(headers)
- # print(body)
- try:
- # print("request = ", requestOptions["url"], "headers = ", options["headers"], "data = ",body)
- response = httpAgent.post(url=requestOptions["url"], headers=options["headers"], data=body)
-
- # print("response.headers = ", response.headers)
-
- if response.status_code // 100 != 2:
- raise Exception("Unexpected response Code {}".format(response.status_code))
- if int(response.headers.get("Content-Length", 0)) > 0:
- body = b""
- body = response.recv_size()
- # print("response body =", body)
- xml = xmlUtils.parseXml(body.decode())
- else:
- print("response None")
- xml = None
-
- if "Set-Cookie" in response.headers:
- cookie = response.headers["Set-Cookie"]
-
- _thread.start_new_thread(callback, (xml,))
- except Exception as e:
- periodic_inform()
-
-
-def startSession(event):
- print("------------------start session ------------ {}".format(event))
- global pendingInform, nextInformTimeout
- nextInformTimeout = None
- pendingInform = False
- requestId = ""
- for i in range(8):
- requestId += random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
-
- def func(body):
- xml = createSoapDocument(requestId, body)
- sendRequest(xml, lambda xml: cpeRequest())
-
- methods.inform(device, event, func)
-
-
-def createFaultResponse(code, message):
- fault = xmlUtils.node(
- "detail",
- {},
- xmlUtils.node("cwmp:Fault", {}, [
- xmlUtils.node("FaultCode", {}, code),
- xmlUtils.node("FaultString", {}, message)
- ])
- )
-
- soapFault = xmlUtils.node("soap-env:Fault", {}, [
- xmlUtils.node("faultcode", {}, "Client"),
- xmlUtils.node("faultstring", {}, "CWMP fault"),
- fault
- ])
-
- return soapFault
-
-
-def cpeRequest():
- pending = methods.getPending()
- if not pending:
- sendRequest(None, lambda xml: handleMethod(xml))
- return
-
- requestId = ""
- for i in range(8):
- requestId += random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
-
- def callback(body, func):
- xml = createSoapDocument(requestId, body)
- sendRequest(xml, lambda xml: func(xml, cpeRequest))
-
- pending(callback)
-
-
-def restart_session(*args):
- # print("wait ~~~~~~~~~~~~~~~~ args {}".format(args))
- global httpAgent
- httpAgent = urequest.Session()
- startSession(None)
-
-
-def _restart_session(*args):
- _thread.start_new_thread(restart_session, (args,))
-
-
-def periodic_inform():
- # print("-------------- periodic_inform------------")
- global nextInformTimeout, control_cwmp
- # print(control_cwmp.get("report_interval"))
- # TODO: 测试注释
- # report_interval = control_cwmp.get("report_interval", 120)
- report_interval = 0
- if report_interval == 0:
- report_interval = 120
- informInterval = int(report_interval) * 1000
- # print("informInterval -> {}".format(informInterval))
- if not nextInformTimeout:
- nextInformTimeout = osTimer()
- nextInformTimeout.stop()
- nextInformTimeout.start(informInterval, 0, _restart_session)
-
-
-def handleMethod(xml):
- global pendingInform, first, cookie
- if not xml:
- httpAgent.close()
- cookie = None
- print("httpAgent.close")
- informInterval = 120
- if "Device.ManagementServer.PeriodicInformInterval" in device:
- informInterval = int(device["Device.ManagementServer.PeriodicInformInterval"][1])
- elif "InternetGatewayDevice.ManagementServer.PeriodicInformInterval" in device:
- informInterval = int(device["InternetGatewayDevice.ManagementServer.PeriodicInformInterval"][1])
- if pendingInform:
- print("pendingInform ~~~ {}".format(pendingInform))
- _thread.start_new_thread(restart_session, ())
- else:
- if not first:
- periodic_inform()
- else:
- print("----- startSession")
- _thread.start_new_thread(restart_session, ())
- first = False
- return
-
- headerElement, bodyElement = None, None
- envelope = xml["children"][0]
- for c in envelope["children"]:
- if c["localName"] == "Header":
- headerElement = c
- elif c["localName"] == "Body":
- bodyElement = c
-
- requestId = None
- for c in headerElement["children"]:
- if c["localName"] == "ID":
- requestId = c["text"]
- break
-
- requestElement = None
- for c in bodyElement["children"]:
- if c["name"].startswith("cwmp:"):
- requestElement = c
- break
- method = methods.INCLODE.get(requestElement["localName"])
-
- if not method:
- body = createFaultResponse(9000, "Method not supported")
- xml = createSoapDocument(requestId, body)
- sendRequest(xml, lambda xml: handleMethod(xml))
- return
-
- def func(body):
- xml = createSoapDocument(requestId, body)
- sendRequest(xml, lambda xml: handleMethod(xml))
-
- method(device, requestElement, func)
-
-
-def listenForConnectionRequests(serialNumber, acsUrlOptions, callback):
- global tr069_server
- tr069_server = TcpServer(acsUrlOptions["server_ip"], acsUrlOptions["server_port"])
- tr069_server.set_callback(callback)
- connectionRequestUrl = "http://{}:{}".format(acsUrlOptions["server_ip"], acsUrlOptions["server_port"])
- if device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]:
- device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"][1] = connectionRequestUrl
- elif device["Device.ManagementServer.ConnectionRequestURL"]:
- device["Device.ManagementServer.ConnectionRequestURL"][1] = connectionRequestUrl
- startSession("6 CONNECTION REQUEST")
- tr069_server.run()
-
-
-def _start(dataModel, serialNumber, acsUrl):
- global device, httpAgent, basicAuth, pendingInform
- device = dataModel
-
- if device.get("DeviceID.SerialNumber"):
- device["DeviceID.SerialNumber"][1] = serialNumber
- if device.get("Device.DeviceInfo.SerialNumber"):
- device["Device.DeviceInfo.SerialNumber"][1] = serialNumber
- if device.get("InternetGatewayDevice.DeviceInfo.SerialNumber"):
- device["InternetGatewayDevice.DeviceInfo.SerialNumber"][1] = serialNumber
-
- username = requestOptions.get("username")
- password = requestOptions.get("password")
- if device.get("Device.ManagementServer.Username"):
- username = device["Device.ManagementServer.Username"][1]
- password = device["Device.ManagementServer.Password"][1]
- elif device.get("InternetGatewayDevice.ManagementServer.Username"):
- username = device["InternetGatewayDevice.ManagementServer.Username"][1]
- password = device["InternetGatewayDevice.ManagementServer.Password"][1]
-
- httpAgent = urequest.Session()
-
- # session.request()
- auth_data = "{}:{}".format(username, password)
- # 加密
- basicAuth = "Basic " + ubinascii.b2a_base64(auth_data)[:-1].decode()
-
- def func(conn, ip, port):
- # print("func ----- {} {}".format(ip, port))
- global pendingInform
- if not nextInformTimeout:
- pendingInform = True
- else:
- nextInformTimeout.stop()
- startSession("6 CONNECTION REQUEST")
-
- listenForConnectionRequests(serialNumber, requestOptions, func)
-
-
-def start(dataModel, serialNumber, acsUrl):
- import utime
- while True:
- try:
- # 获取tr069控制参数
- _start(dataModel, serialNumber, acsUrl)
- except Exception as e:
- usys.print_exception(e)
- # print("--------------------------------------111")
- nextInformTimeout.stop()
- utime.sleep(120)
- else:
- break
-
-
-class CWMPRepository(object):
- _instance = None
- _file = None
-
- @classmethod
- def get(cls, key):
- print('get data model instance -> ', cls._instance)
- return cls._instance[key]
-
- @classmethod
- def post(cls, key, value):
- print('set data model instance -> ', cls._instance)
- cls._instance[key] = value
- cls.store()
-
- @classmethod
- def post_all(cls, data):
- for item in data:
- print('set data model instance -> ', cls._instance)
- cls._instance[item['key']] = item['value']
- cls.store()
-
- @classmethod
- def data(cls):
- return cls._instance
-
- @classmethod
- def store(cls):
- with open(cls._file, 'w') as f:
- json.dump(cls._instance, f)
-
- @classmethod
- def _read(cls):
- with open(cls._file, 'r') as f:
- # 使用json.load()方法将JSON数据读取为Python对象
- return json.load(f)
-
- @classmethod
- def build(cls, file):
- if not cls._instance:
- cls._file = file
- cls._instance = cls._read()
- return cls
-
-
-def SetRequestOptions(data, c_data):
- print('SetRequestOptions -> ', data)
- global requestOptions, control_cwmp
- requestOptions = data
- control_cwmp = c_data
-
-
-if __name__ == '__main__':
- _thread.stack_size(32 * 1024)
- requestOptions = {
- "protocol": 'http:',
- "host": '39.106.195.193:9090',
- "port": '9090',
- "hostname": '39.106.195.193',
- "server_ip": '0.0.0.0',
- "server_port": 8001,
- "pathname": '/ACS-server/ACS/pawn',
- "path": '/ACS-server/ACS/pawn',
- "href": 'http://39.106.195.193:9090/ACS-server/ACS/pawn',
- "url": 'http://39.106.195.193:9090/ACS-server/ACS/pawn'
- }
- import ujson as json
-
- with open('usr/data_model_2021.json', 'r') as f:
- # 使用json.load()方法将JSON数据读取为Python对象
- data = json.load(f)
- serialNumber = "000021" # 设备序列号
- start(data, serialNumber, "http://39.106.195.193:9090/ACS-server/ACS/xjin")
+# Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import _thread
+import usys
+import osTimer
+import ubinascii
+import urandom as random
+from usr import urequest
+# import request
+from usr import methods
+from usr import xmlUtils
+import ujson as json
+from usr.tcp_server import TcpServer
+
+NAMESPACES = {
+ "soap-enc": "http://schemas.xmlsoap.org/soap/encoding/",
+ "soap-env": "http://schemas.xmlsoap.org/soap/envelope/",
+ "xsd": "http://www.w3.org/2001/XMLSchema",
+ "xsi": "http://www.w3.org/2001/XMLSchema-instance",
+ "cwmp": "urn:dslforum-org:cwmp-1-0"
+}
+
+nextInformTimeout = osTimer()
+pendingInform = False
+http = None
+requestOptions = None
+control_cwmp = None
+device = None
+httpAgent = None
+basicAuth = None
+cookie = None
+first = True
+tr069_server = None
+
+
+def createSoapDocument(id, body):
+ headerNode = xmlUtils.node(
+ "soap-env:Header",
+ {},
+ xmlUtils.node(
+ "cwmp:ID",
+ {"soap-env:mustUnderstand": 1},
+ id
+ )
+ )
+
+ bodyNode = xmlUtils.node("soap-env:Body", {}, body)
+ namespaces = {}
+ for prefix in NAMESPACES:
+ namespaces["xmlns:{}".format(prefix)] = NAMESPACES[prefix]
+
+ env = xmlUtils.node("soap-env:Envelope", namespaces, [headerNode, bodyNode])
+
+ return "\n{}".format(env)
+
+
+def sendRequest(xml, callback):
+ global httpAgent, cookie
+ headers = {}
+ body = xml or ""
+
+ headers["Content-Type"] = "text/xml; charset=\"utf-8\""
+ headers["Accept-Encoding"] = "gzip, deflate"
+ headers["Accept"] = "*/*"
+ headers["Authorization"] = basicAuth
+
+ if cookie:
+ headers["Cookie"] = cookie
+
+ options = {
+ "method": "POST",
+ "headers": headers,
+ }
+ options.update(requestOptions)
+ print("------------------ headers & body ------------------")
+ try:
+ # print("request = ", requestOptions["url"], "headers = ", options["headers"], "data = ",body)
+ response = httpAgent.post(url=requestOptions["url"], headers=options["headers"], data=body)
+
+ # print("response.headers = ", response.headers)
+
+ if response.status_code // 100 != 2:
+ raise Exception("Unexpected response Code {}".format(response.status_code))
+ if int(response.headers.get("Content-Length", 0)) > 0:
+ body = b""
+ body = response.recv_size()
+ # print("response body =", body)
+ xml = xmlUtils.parseXml(body.decode())
+ else:
+ print("response None")
+ xml = None
+
+ if "Set-Cookie" in response.headers:
+ cookie = response.headers["Set-Cookie"]
+
+ _thread.start_new_thread(callback, (xml,))
+ except Exception as e:
+ periodic_inform()
+
+
+def startSession(event):
+ print("------------------start session ------------ {}".format(event))
+ global pendingInform, nextInformTimeout
+ nextInformTimeout = None
+ pendingInform = False
+ requestId = ""
+ for i in range(8):
+ requestId += random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
+
+ def func(body):
+ xml = createSoapDocument(requestId, body)
+ sendRequest(xml, lambda xml: cpeRequest())
+
+ methods.inform(device, event, func)
+
+
+def createFaultResponse(code, message):
+ fault = xmlUtils.node(
+ "detail",
+ {},
+ xmlUtils.node("cwmp:Fault", {}, [
+ xmlUtils.node("FaultCode", {}, code),
+ xmlUtils.node("FaultString", {}, message)
+ ])
+ )
+
+ soapFault = xmlUtils.node("soap-env:Fault", {}, [
+ xmlUtils.node("faultcode", {}, "Client"),
+ xmlUtils.node("faultstring", {}, "CWMP fault"),
+ fault
+ ])
+
+ return soapFault
+
+
+def cpeRequest():
+ pending = methods.getPending()
+ if not pending:
+ sendRequest(None, lambda xml: handleMethod(xml))
+ return
+
+ requestId = ""
+ for i in range(8):
+ requestId += random.choice('abcdefghijklmnopqrstuvwxyz0123456789')
+
+ def callback(body, func):
+ xml = createSoapDocument(requestId, body)
+ sendRequest(xml, lambda xml: func(xml, cpeRequest))
+
+ pending(callback)
+
+
+def restart_session(*args):
+ # print("wait ~~~~~~~~~~~~~~~~ args {}".format(args))
+ global httpAgent
+ httpAgent = urequest.Session()
+ startSession(None)
+
+
+def _restart_session(*args):
+ _thread.start_new_thread(restart_session, (args,))
+
+
+def periodic_inform():
+ # print("-------------- periodic_inform------------")
+ global nextInformTimeout, control_cwmp
+ # print(control_cwmp.get("report_interval"))
+ # report_interval = control_cwmp.get("report_interval", 120)
+ report_interval = 0
+ if report_interval == 0:
+ report_interval = 120
+ informInterval = int(report_interval) * 1000
+ # print("informInterval -> {}".format(informInterval))
+ if not nextInformTimeout:
+ nextInformTimeout = osTimer()
+ nextInformTimeout.stop()
+ nextInformTimeout.start(informInterval, 0, _restart_session)
+
+
+def handleMethod(xml):
+ global pendingInform, first, cookie
+ if not xml:
+ httpAgent.close()
+ cookie = None
+ print("httpAgent.close")
+ informInterval = 120
+ if "Device.ManagementServer.PeriodicInformInterval" in device:
+ informInterval = int(device["Device.ManagementServer.PeriodicInformInterval"][1])
+ elif "InternetGatewayDevice.ManagementServer.PeriodicInformInterval" in device:
+ informInterval = int(device["InternetGatewayDevice.ManagementServer.PeriodicInformInterval"][1])
+ if pendingInform:
+ print("pendingInform ~~~ {}".format(pendingInform))
+ _thread.start_new_thread(restart_session, ())
+ else:
+ if not first:
+ periodic_inform()
+ else:
+ print("----- startSession")
+ _thread.start_new_thread(restart_session, ())
+ first = False
+ return
+
+ headerElement, bodyElement = None, None
+ envelope = xml["children"][0]
+ for c in envelope["children"]:
+ if c["localName"] == "Header":
+ headerElement = c
+ elif c["localName"] == "Body":
+ bodyElement = c
+
+ requestId = None
+ for c in headerElement["children"]:
+ if c["localName"] == "ID":
+ requestId = c["text"]
+ break
+
+ requestElement = None
+ for c in bodyElement["children"]:
+ if c["name"].startswith("cwmp:"):
+ requestElement = c
+ break
+ method = methods.INCLODE.get(requestElement["localName"])
+
+ if not method:
+ body = createFaultResponse(9000, "Method not supported")
+ xml = createSoapDocument(requestId, body)
+ sendRequest(xml, lambda xml: handleMethod(xml))
+ return
+
+ def func(body):
+ xml = createSoapDocument(requestId, body)
+ sendRequest(xml, lambda xml: handleMethod(xml))
+
+ method(device, requestElement, func)
+
+
+def listenForConnectionRequests(serialNumber, acsUrlOptions, callback):
+ global tr069_server
+ tr069_server = TcpServer(acsUrlOptions["server_ip"], acsUrlOptions["server_port"])
+ tr069_server.set_callback(callback)
+ connectionRequestUrl = "http://{}:{}".format(acsUrlOptions["server_ip"], acsUrlOptions["server_port"])
+ if device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"]:
+ device["InternetGatewayDevice.ManagementServer.ConnectionRequestURL"][1] = connectionRequestUrl
+ elif device["Device.ManagementServer.ConnectionRequestURL"]:
+ device["Device.ManagementServer.ConnectionRequestURL"][1] = connectionRequestUrl
+ startSession("6 CONNECTION REQUEST")
+ tr069_server.run()
+
+
+def _start(dataModel, serialNumber, acsUrl):
+ global device, httpAgent, basicAuth, pendingInform
+ device = dataModel
+
+ if device.get("DeviceID.SerialNumber"):
+ device["DeviceID.SerialNumber"][1] = serialNumber
+ if device.get("Device.DeviceInfo.SerialNumber"):
+ device["Device.DeviceInfo.SerialNumber"][1] = serialNumber
+ if device.get("InternetGatewayDevice.DeviceInfo.SerialNumber"):
+ device["InternetGatewayDevice.DeviceInfo.SerialNumber"][1] = serialNumber
+
+ username = requestOptions.get("username")
+ password = requestOptions.get("password")
+ if device.get("Device.ManagementServer.Username"):
+ username = device["Device.ManagementServer.Username"][1]
+ password = device["Device.ManagementServer.Password"][1]
+ elif device.get("InternetGatewayDevice.ManagementServer.Username"):
+ username = device["InternetGatewayDevice.ManagementServer.Username"][1]
+ password = device["InternetGatewayDevice.ManagementServer.Password"][1]
+
+ httpAgent = urequest.Session()
+
+ # session.request()
+ auth_data = "{}:{}".format(username, password)
+ # Encrypt
+ basicAuth = "Basic " + ubinascii.b2a_base64(auth_data)[:-1].decode()
+
+ def func(conn, ip, port):
+ # print("func ----- {} {}".format(ip, port))
+ global pendingInform
+ if not nextInformTimeout:
+ pendingInform = True
+ else:
+ nextInformTimeout.stop()
+ startSession("6 CONNECTION REQUEST")
+
+ listenForConnectionRequests(serialNumber, requestOptions, func)
+
+
+def start(dataModel, serialNumber, acsUrl):
+ import utime
+ while True:
+ try:
+ # Retrieve TR-069 control parameters.
+ _start(dataModel, serialNumber, acsUrl)
+ except Exception as e:
+ usys.print_exception(e)
+ nextInformTimeout.stop()
+ utime.sleep(120)
+ else:
+ break
+
+
+class CWMPRepository(object):
+ _instance = None
+ _file = None
+
+ @classmethod
+ def get(cls, key):
+ print('get data model instance -> ', cls._instance)
+ return cls._instance[key]
+
+ @classmethod
+ def post(cls, key, value):
+ print('set data model instance -> ', cls._instance)
+ cls._instance[key] = value
+ cls.store()
+
+ @classmethod
+ def post_all(cls, data):
+ for item in data:
+ print('set data model instance -> ', cls._instance)
+ cls._instance[item['key']] = item['value']
+ cls.store()
+
+ @classmethod
+ def data(cls):
+ return cls._instance
+
+ @classmethod
+ def store(cls):
+ with open(cls._file, 'w') as f:
+ json.dump(cls._instance, f)
+
+ @classmethod
+ def _read(cls):
+ with open(cls._file, 'r') as f:
+ # Use the json.load() method to read JSON data into a Python object.
+ return json.load(f)
+
+ @classmethod
+ def build(cls, file):
+ if not cls._instance:
+ cls._file = file
+ cls._instance = cls._read()
+ return cls
+
+
+def SetRequestOptions(data, c_data):
+ print('SetRequestOptions -> ', data)
+ global requestOptions, control_cwmp
+ requestOptions = data
+ control_cwmp = c_data
+
+
+if __name__ == '__main__':
+ _thread.stack_size(32 * 1024)
+ requestOptions = {
+ "protocol": 'http:',
+ "host": '39.106.195.193:9090',
+ "port": '9090',
+ "hostname": '39.106.195.193',
+ "server_ip": '0.0.0.0',
+ "server_port": 8001,
+ "pathname": '/ACS-server/ACS/pawn06',
+ "path": '/ACS-server/ACS/pawn06',
+ "href": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06',
+ "url": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06'
+ }
+ import ujson as json
+
+ with open('usr/data_model_2021.json', 'r') as f:
+ # Use the json.load() method to read JSON data into a Python object.
+ data = json.load(f)
+ serialNumber = "863141050702530" # imei
+ start(data, serialNumber, "http://39.106.195.193:9090/ACS-server/ACS/pawn06")
diff --git a/docs/en/TR069_User_Guide.md b/docs/en/TR069_User_Guide.md
new file mode 100644
index 0000000..e072628
--- /dev/null
+++ b/docs/en/TR069_User_Guide.md
@@ -0,0 +1,101 @@
+# QuecPython-TR069-CWMP User Guide
+
+This document mainly describes the usage of TR069 and QuecPython.
+
+
+
+## Overview
+
+TR-069 is a protocol used for remote management and configuration of devices, typically used by Internet Service Providers (ISPs) to manage devices provided to customers, such as routers, modems, and optical network terminals. Here are the main features and instructions for use of TR-069:
+
+1. **Device Configuration**: Initially, the device needs to know the URL, username, and password of the Auto Configuration Server (ACS) in order to connect to the ACS. This information is usually provided by the ISP.
+2. **Remote Management**: The ACS can remotely manage devices, including configuration changes, firmware upgrades, and device reboots. These operations can be performed through TR-069 communication with the device.
+3. **Parameter Query**: The ACS can query the device's configuration parameters and status information. This helps monitor device performance, troubleshoot, and diagnose faults.
+4. **Regular Connection**: Devices typically connect to the ACS regularly to check for new configurations or instructions. This is usually achieved by periodically sending heartbeat signals or performing scheduled ACS requests.
+5. **Security**: TR-069 supports secure communication, typically using the HTTPS protocol to protect data transmission between the ACS and devices. Communication between the device and ACS can be authenticated using a username and password to ensure security.
+6. **Event Notification**: Devices can send event notifications to the ACS, such as error reports, alerts, and status changes. The ACS can subscribe to these events to take appropriate actions in a timely manner.
+7. **Configuration Profiles**: The ACS can send configuration profiles to devices so that devices can automatically perform corresponding settings. This includes network settings, port mapping, firewall rules, etc.
+8. **Device Identification**: The TR-069 protocol uses a Customer Premises Equipment (CPE) identifier to uniquely identify devices, usually the device's serial number or other unique identifier.
+
+Overall, the TR-069 protocol provides a standardized way to remotely manage and configure devices, enabling ISPs to more easily manage a large number of devices while also providing better service and support to end-users. Using TR-069 requires proper configuration and communication between the device and ACS to ensure normal operation.
+
+## Components
+
+TR-069 (Technical Report 069) is a protocol that includes multiple components, defining a framework and specifications for remote management and configuration of devices. Here are some of the main components of TR-069:
+
+1. **CPE (Customer Premises Equipment)**: CPE refers to devices located at the customer's premises or home, such as routers, modems, and optical network terminals. CPE is the object of management in TR-069, meaning the devices that need remote management and configuration.
+2. **ACS (Auto Configuration Server)**: The ACS is the server-side component of TR-069, typically hosted by the Internet Service Provider (ISP) or device manufacturer. The ACS is used for remote management of CPE, including configuration, upgrades, monitoring, and troubleshooting.
+3. **CWMP (CPE WAN Management Protocol)**: CWMP is the communication protocol within the TR-069 protocol, defining the communication specifications between CPE and ACS. It stipulates the details of message format, method calls, notifications, and data models.
+4. **Data Model**: TR-069 defines a set of data models used to describe the parameters and configuration of CPE. These data models organize the information of devices, allowing the ACS to query and configure various properties of the device.
+5. **RPC (Remote Procedure Call)**: TR-069 uses RPC to perform remote method calls, enabling the ACS to interact with the CPE. Through RPC, the ACS can send commands to the CPE, such as configuration changes, firmware upgrades, and parameter queries.
+6. **Security**: The TR-069 protocol focuses on the security of communication. Typically, TR-069 communication is encrypted using the HTTPS protocol to ensure the confidentiality and integrity of data. Additionally, communication between the device and ACS may require authentication to ensure the legitimacy of the communication.
+7. **Event Notification**: TR-069 supports sending event notifications from devices to the ACS, including error reports, status changes, and alerts. The ACS can subscribe to these events to respond in a timely manner.
+8. **Configuration Profiles**: The ACS can send configuration profiles to the CPE so that devices can automatically perform corresponding settings. This facilitates consistent configuration in large-scale deployments.
+
+TR-069 is a complex protocol that includes multiple components for remote management and configuration of devices. It allows ISPs and device manufacturers to effectively manage a large number of devices, providing better services and support, and ensuring device performance and security.
+
+The Quecpython version of TR069 primarily combines customizable CWMP and RPC features to meet customer needs. Customers only need to register the corresponding time to handle the corresponding RPC events. We also support TR069 server-side mode, not just client mode, to resolve the loop of event notifications.
+
+## Operation
+
+### ACS Platform Registration
+
+Register on the ACS platform:
+
+> http://39.106.195.193:9090/acscloud/index.newinit.action
+
+
+
+### Obtain Platform Information
+
+
+
+### Request Configuration
+
+Configure the startup parameters in **main.py**:
+
+
+
+**Request Options Configuration Explanation**
+
+Mainly used for configuration:
+
+| Property Name | Example Value | Description |
+| ------------- | ------------------------------------------------ | ------------------------------------------------------------ |
+| protocol | http | Protocol of the URL (http or https) |
+| host | 39.106.195.193:9090 | Host part of the URL, including hostname and port number |
+| port | 9090 | Port number of the URL |
+| hostname | 39.106.195.193 | Hostname (excluding port number) |
+| server_ip | 0.0.0.0 | Fixed IP address of the server |
+| server_port | 8001 | Configurable port number of the server |
+| pathname | /ACS-server/ACS/pawn06 | Path part of the URL (resource address of the ACS server) , For example, the resource address of http://39.106.195.193:9090/ACS-server/ACS/pawn06 is "/ACS-server/ACS/pawn06" |
+| path | /ACS-server/ACS/pawn06 | Path part of the URL (same as pathname) |
+| href | http://39.106.195.193:9090/ACS-server/ACS/pawn06 | Full URL, including protocol, host, port, and path parts |
+| url | http://39.106.195.193:9090/ACS-server/ACS/pawn06 | Full URL, including protocol, host, port, and path parts |
+
+Example:
+
+```json
+{
+ "protocol": 'http:',
+ "host": '39.106.195.193:9090',
+ "port": '9090',
+ "hostname": '39.106.195.193',
+ "server_ip": '0.0.0.0',
+ "server_port": 8001,
+ "pathname": '/ACS-server/ACS/pawn06',
+ "path": '/ACS-server/ACS/pawn06',
+ "href": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06',
+ "url": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06'
+}
+```
+
+**Configure Serial Number**
+
+The serial number acts like a name in CPE, generally replaced by **IMEI**.
+
+**Start Business**
+
+Download the code to the module and run it to complete the full CPE startup process. The device online status can be seen on the platform device management.
+
+
\ No newline at end of file
diff --git a/docs/media/image-20240625133917262.png b/docs/media/image-20240625133917262.png
new file mode 100644
index 0000000..87691b0
Binary files /dev/null and b/docs/media/image-20240625133917262.png differ
diff --git a/docs/media/image-20240625134122695.png b/docs/media/image-20240625134122695.png
new file mode 100644
index 0000000..193881f
Binary files /dev/null and b/docs/media/image-20240625134122695.png differ
diff --git a/docs/media/image-20240625134527956.png b/docs/media/image-20240625134527956.png
new file mode 100644
index 0000000..4da3e73
Binary files /dev/null and b/docs/media/image-20240625134527956.png differ
diff --git a/docs/media/image-20240625134756546.png b/docs/media/image-20240625134756546.png
new file mode 100644
index 0000000..56bf970
Binary files /dev/null and b/docs/media/image-20240625134756546.png differ
diff --git "a/docs/zh/TR069\344\275\277\347\224\250\350\257\264\346\230\216.md" "b/docs/zh/TR069\344\275\277\347\224\250\350\257\264\346\230\216.md"
new file mode 100644
index 0000000..e3c22fa
--- /dev/null
+++ "b/docs/zh/TR069\344\275\277\347\224\250\350\257\264\346\230\216.md"
@@ -0,0 +1,110 @@
+# QuecPython-TR069-CWMP使用说明
+
+此篇文档主要描述TR069和QuecPython的使用介绍文档
+
+
+
+## 概述
+
+TR-069是一种用于远程管理和配置设备的协议,通常由互联网服务提供商(ISP)用于管理提供给客户的设备,如路由器、调制解调器和光猫。以下是TR-069的主要功能和使用说明:
+
+1. **设备配置**:首先,设备需要知道ACS(Auto Configuration Server)的URL、ACS的用户名和密码等信息,以便连接到ACS。这些信息通常由ISP提供。
+2. **远程管理**:ACS可以远程管理设备,包括配置更改、固件升级、设备重启等操作。这些操作可以通过与设备之间的TR-069通信来执行。
+3. **参数查询**:ACS可以查询设备的配置参数和状态信息。这有助于监视设备性能、故障排除和故障诊断。
+4. **定期连接**:设备通常会定期连接到ACS,以检查是否有新的配置或指令。这通常是通过定期发送心跳信号或定期执行ACS请求来实现的。
+5. **安全性**:TR-069支持安全的通信,通常使用HTTPS协议来保护ACS和设备之间的数据传输。设备和ACS之间的通信可以使用用户名和密码进行身份验证,以确保安全性。
+6. **事件通知**:设备可以向ACS发送事件通知,例如错误报告、警报和状态更改。ACS可以订阅这些事件,以便及时采取适当的措施。
+7. **配置文件**:ACS可以向设备发送配置文件,以便设备自动进行相应的设置。这包括网络设置、端口映射、防火墙规则等。
+8. **设备识别**:TR-069协议使用CPE(Customer Premises Equipment)标识符来唯一标识设备,通常是设备的序列号或其他唯一标识符。
+
+总的来说,TR-069协议提供了一种标准化的方式来远程管理和配置设备,使ISP能够更轻松地管理大量设备,同时也为终端用户提供了更好的服务和支持。使用TR-069需要设备和ACS之间的合适配置和通信,以确保正常运行。
+
+
+
+## 组成
+
+TR-069(Technical Report 069)是一个包含多个组成部分的协议,它定义了用于远程管理和配置设备的框架和规范。以下是TR-069的一些主要组成部分:
+
+1. **CPE(Customer Premises Equipment)**:CPE指的是位于客户设备或用户家庭的设备,如路由器、调制解调器、光猫等。CPE是TR-069的管理对象,即需要远程管理和配置的设备。
+2. **ACS(Auto Configuration Server)**:ACS是TR-069的服务器端组件,通常由互联网服务提供商(ISP)或设备制造商托管。ACS用于远程管理CPE,包括配置、升级、监控和故障排除等操作。
+3. **CWMP(CPE WAN Management Protocol)**:CWMP是TR-069协议中的通信协议,定义了CPE与ACS之间的通信规范。它规定了消息格式、方法调用、通知和数据模型等方面的细节。
+4. **数据模型**:TR-069定义了一组数据模型,用于描述CPE的参数和配置。这些数据模型组织了设备的信息,允许ACS查询和配置设备的各种属性。
+5. **RPC(Remote Procedure Call)**:TR-069使用RPC来执行远程方法调用,使ACS能够与CPE交互。通过RPC,ACS可以向CPE发送命令,例如配置更改、固件升级和参数查询。
+6. **安全性**:TR-069协议关注通信的安全性。通常,TR-069通信是通过HTTPS协议进行加密的,以确保数据的机密性和完整性。另外,设备和ACS之间的通信可能需要身份验证,以确保通信的合法性。
+7. **事件通知**:TR-069支持设备向ACS发送事件通知,包括错误报告、状态更改和警报。ACS可以订阅这些事件以及时响应。
+8. **配置文件**:ACS可以向CPE发送配置文件,以便设备自动进行相应的设置。这使得在大规模部署中更容易实现一致性配置。
+
+TR-069是一个复杂的协议,包含多个组成部分,用于实现设备的远程管理和配置。它允许ISP和设备制造商有效地管理大量设备,提供更好的服务和支持,并确保设备的性能和安全性。
+
+**Quecpython**版本的**TR069**主要结合了, 目前主要结合了**CWMP**和**RPC**可定制的功能, 以满足客户, 需求, 客户只需要注册对应的时间处理即可处理对应的**RPC**事件. 来参与**ACS**与**CPE**之间的业务交互, 同时我们支持**TR069**端侧**Server**模式, 而不再是单纯的**Client**来解决事件的循环通知
+
+
+
+
+
+## 运行
+
+### ACS平台注册
+
+注册ACS平台
+
+> http://39.106.195.193:9090/acscloud/index.newinit.action
+
+
+
+### 获取平台信息
+
+
+
+### 请求配置
+
+配置**main.py**中的启动参数
+
+
+
+**requestOptions配置说明**
+
+主要用于配置
+
+| 属性名 | 示例值 | 说明 |
+| ----------- | ------------------------------------------------ | ------------------------------------------------------------ |
+| protocol | http | URL的协议(http或https) |
+| host | 39.106.195.193:9090 | URL的主机部分,包括主机名和端口号 |
+| port | 9090 | URL的端口号 |
+| hostname | 39.106.195.193 | 主机名(不包括端口号) |
+| server_ip | 0.0.0.0 | 服务器的IP地址(固定) |
+| server_port | 8001 | 服务器的端口号(可配置) |
+| pathname | /ACS-server/ACS/pawn06 | URL的路径部分 (ACS的服务器的资源地址)
例如: http://39.106.195.193:9090/ACS-server/ACS/pawn06的资源地址是
/ACS-server/ACS/pawn06 |
+| path | /ACS-server/ACS/pawn06 | URL的路径部分(与pathname相同) |
+| href | http://39.106.195.193:9090/ACS-server/ACS/pawn06 | 完整的URL,包括协议、主机、端口和路径部分 |
+| url | http://39.106.195.193:9090/ACS-server/ACS/pawn06 | 完整的URL,包括协议、主机、端口和路径部分 |
+
+示例:
+
+```json
+{
+ "protocol": 'http:',
+ "host": '39.106.195.193:9090',
+ "port": '9090',
+ "hostname": '39.106.195.193',
+ "server_ip": '0.0.0.0',
+ "server_port": 8001,
+ "pathname": '/ACS-server/ACS/pawn06',
+ "path": '/ACS-server/ACS/pawn06',
+ "href": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06',
+ "url": 'http://39.106.195.193:9090/ACS-server/ACS/pawn06'
+}
+```
+
+**配置序列号**
+
+序列号在CPE的作用就等于是个名称, 一般采用**IMEI**代替
+
+**启动业务**
+
+代码下载到模组中运行,即可完成完整的CPE启动过程, 平台上可以从设备管理上看到设备在线状态
+
+
+
+
+
diff --git a/media/image-20231110152705998.png b/media/image-20231110152705998.png
deleted file mode 100644
index e4db4ec..0000000
Binary files a/media/image-20231110152705998.png and /dev/null differ
diff --git a/media/image-20231110153342967.png b/media/image-20231110153342967.png
deleted file mode 100644
index 87a2104..0000000
Binary files a/media/image-20231110153342967.png and /dev/null differ
diff --git a/media/image-20231110160215621.png b/media/image-20231110160215621.png
deleted file mode 100644
index f509a7d..0000000
Binary files a/media/image-20231110160215621.png and /dev/null differ
diff --git a/readme.md b/readme.md
index bd92f4f..3c50e8c 100644
--- a/readme.md
+++ b/readme.md
@@ -1,110 +1,32 @@
-# QuecPython-TR069-CWMP使用说明
-
-此篇文档主要描述TR069和QuecPython的使用介绍文档
-
-
-
-## 概述
-
-TR-069是一种用于远程管理和配置设备的协议,通常由互联网服务提供商(ISP)用于管理提供给客户的设备,如路由器、调制解调器和光猫。以下是TR-069的主要功能和使用说明:
-
-1. **设备配置**:首先,设备需要知道ACS(Auto Configuration Server)的URL、ACS的用户名和密码等信息,以便连接到ACS。这些信息通常由ISP提供。
-2. **远程管理**:ACS可以远程管理设备,包括配置更改、固件升级、设备重启等操作。这些操作可以通过与设备之间的TR-069通信来执行。
-3. **参数查询**:ACS可以查询设备的配置参数和状态信息。这有助于监视设备性能、故障排除和故障诊断。
-4. **定期连接**:设备通常会定期连接到ACS,以检查是否有新的配置或指令。这通常是通过定期发送心跳信号或定期执行ACS请求来实现的。
-5. **安全性**:TR-069支持安全的通信,通常使用HTTPS协议来保护ACS和设备之间的数据传输。设备和ACS之间的通信可以使用用户名和密码进行身份验证,以确保安全性。
-6. **事件通知**:设备可以向ACS发送事件通知,例如错误报告、警报和状态更改。ACS可以订阅这些事件,以便及时采取适当的措施。
-7. **配置文件**:ACS可以向设备发送配置文件,以便设备自动进行相应的设置。这包括网络设置、端口映射、防火墙规则等。
-8. **设备识别**:TR-069协议使用CPE(Customer Premises Equipment)标识符来唯一标识设备,通常是设备的序列号或其他唯一标识符。
-
-总的来说,TR-069协议提供了一种标准化的方式来远程管理和配置设备,使ISP能够更轻松地管理大量设备,同时也为终端用户提供了更好的服务和支持。使用TR-069需要设备和ACS之间的合适配置和通信,以确保正常运行。
-
-
-
-## 组成
-
-TR-069(Technical Report 069)是一个包含多个组成部分的协议,它定义了用于远程管理和配置设备的框架和规范。以下是TR-069的一些主要组成部分:
-
-1. **CPE(Customer Premises Equipment)**:CPE指的是位于客户设备或用户家庭的设备,如路由器、调制解调器、光猫等。CPE是TR-069的管理对象,即需要远程管理和配置的设备。
-2. **ACS(Auto Configuration Server)**:ACS是TR-069的服务器端组件,通常由互联网服务提供商(ISP)或设备制造商托管。ACS用于远程管理CPE,包括配置、升级、监控和故障排除等操作。
-3. **CWMP(CPE WAN Management Protocol)**:CWMP是TR-069协议中的通信协议,定义了CPE与ACS之间的通信规范。它规定了消息格式、方法调用、通知和数据模型等方面的细节。
-4. **数据模型**:TR-069定义了一组数据模型,用于描述CPE的参数和配置。这些数据模型组织了设备的信息,允许ACS查询和配置设备的各种属性。
-5. **RPC(Remote Procedure Call)**:TR-069使用RPC来执行远程方法调用,使ACS能够与CPE交互。通过RPC,ACS可以向CPE发送命令,例如配置更改、固件升级和参数查询。
-6. **安全性**:TR-069协议关注通信的安全性。通常,TR-069通信是通过HTTPS协议进行加密的,以确保数据的机密性和完整性。另外,设备和ACS之间的通信可能需要身份验证,以确保通信的合法性。
-7. **事件通知**:TR-069支持设备向ACS发送事件通知,包括错误报告、状态更改和警报。ACS可以订阅这些事件以及时响应。
-8. **配置文件**:ACS可以向CPE发送配置文件,以便设备自动进行相应的设置。这使得在大规模部署中更容易实现一致性配置。
-
-TR-069是一个复杂的协议,包含多个组成部分,用于实现设备的远程管理和配置。它允许ISP和设备制造商有效地管理大量设备,提供更好的服务和支持,并确保设备的性能和安全性。
-
-**Quecpython**版本的**TR069**主要结合了, 目前主要结合了**CWMP**和**RPC**可定制的功能, 以满足客户, 需求, 客户只需要注册对应的时间处理即可处理对应的**RPC**事件. 来参与**ACS**与**CPE**之间的业务交互, 同时我们支持**TR069**端侧**Server**模式, 而不再是单纯的**Client**来解决事件的循环通知
-
-
-
-
-
-## 运行
-
-### ACS平台注册
-
-注册ACS平台
-
-> http://39.106.195.193:9090/acscloud/index.newinit.action
-
-
-
-### 获取平台信息
-
-
-
-### 请求配置
-
-主要是配置**simulation.py**中的启动配置, 代码在**simulation.py**的最后
-
-
-
-**requestOptions配置说明**
-
-主要用于配置
-
-| 属性名 | 示例值 | 说明 |
-| ----------- | ------------------------------------------------ | ------------------------------------------------------------ |
-| protocol | 'http:' | URL的协议(http或https) |
-| host | '39.106.195.193:9090' | URL的主机部分,包括主机名和端口号 |
-| port | '9090' | URL的端口号 |
-| hostname | '39.106.195.193' | 主机名(不包括端口号) |
-| server_ip | '0.0.0.0' | 服务器的IP地址(固定) |
-| server_port | 8001 | 服务器的端口号(可配置) |
-| pathname | '/ACS-server/ACS/pawn' | URL的路径部分 (ACS的服务器的资源地址)
例如: http://39.106.195.193:9090/ACS-server/ACS/pawn的资源地址是
/ACS-server/ACS/pawn |
-| path | '/ACS-server/ACS/pawn' | URL的路径部分(与pathname相同) |
-| href | 'http://39.106.195.193:9090/ACS-server/ACS/pawn' | 完整的URL,包括协议、主机、端口和路径部分 |
-| url | 'http://39.106.195.193:9090/ACS-server/ACS/pawn' | 完整的URL,包括协议、主机、端口和路径部分 |
-
-例:
-
-```json
-{
- "protocol": 'http:',
- "host": '39.106.195.193:9090',
- "port": '9090',
- "hostname": '39.106.195.193',
- "server_ip": '0.0.0.0',
- "server_port": 8001,
- "pathname": '/ACS-server/ACS/pawn',
- "path": '/ACS-server/ACS/pawn',
- "href": 'http://39.106.195.193:9090/ACS-server/ACS/pawn',
- "url": 'http://39.106.195.193:9090/ACS-server/ACS/pawn'
-}
-```
-
-**配置序列号**
-
-序列号在CPE的作用就等于是个名称, 一般采用**IMEI**代替
-
-**启动业务**
-
-即可完成完整的CPE启动过程, 平台上可以从设备管理上看到设备在线状态
-
-
-
-
-
+# QuecPython-TR069-CWMP
+
+[中文](./README_ZH.md) | English
+
+## Overview
+
+TR-069, fully known as "Technical Report 069," is an application layer protocol established by the Broadband Forum (originally DSL Forum). It is used for remote management and configuration of broadband devices for home and business users, such as home gateways, routers, and VoIP phones. The protocol is also known as the CPE WAN Management Protocol (CWMP), enabling Internet Service Providers (ISPs) to automatically configure, monitor, and manage end-devices connected to the network from a central server.
+
+The Quecpython version of **TR069** mainly integrates customizable **CWMP** and **RPC** features to meet customer needs. Customers only need to register the corresponding time to handle the respective **RPC** events to participate in the business interaction between **ACS** and **CPE**.
+
+## Usage
+
+- [TR069 Usage Instructions](./docs/en/TR069_User_Guide.md)
+- [Sample Code](./code/main.py)
+
+## Contribution
+
+We welcome contributions to improve this project! Please follow these steps to contribute:
+
+1. Fork this repository.
+2. Create a new branch (`git checkout -b feature/your-feature`).
+3. Commit your changes (`git commit -m 'Add your feature'`).
+4. Push to the branch (`git push origin feature/your-feature`).
+5. Open a Pull Request.
+
+## License
+
+This project is licensed under the Apache License. For more details, please see the [LICENSE](./LICENSE) file.
+
+## Support
+
+If you have any questions or need support, please refer to the [QuecPython Documentation](https://python.quectel.com/doc) or open an issue in this repository.
\ No newline at end of file