1. 项目概述从“数据孤岛”到“知识中枢”的蜕变在物联网IoT项目尤其是像SenseCAP这样的环境监测生态中我们常常面临一个典型的“数据富集知识贫瘠”的困境。传感器Watcher7x24小时不间断地采集着温度、湿度、气压、光照、二氧化碳浓度等海量数据这些数据通过网关汇聚到云端平台形成了一张庞大的数据网络。然而对于一线运维人员、项目管理者乃至数据分析师而言这些原始数据流就像一座座彼此隔离的“数据孤岛”——你知道数据在那里但想要快速理解某个传感器的历史健康状况、排查一次异常波动的根源、或者向新同事解释某个监测点的部署逻辑时往往需要翻遍多个平台查历史数据要去云平台控制台看设备硬件信息得找采购清单或部署文档了解安装现场的物理环境可能还得去翻聊天记录或现场照片。这种信息碎片化带来的效率损耗和决策延迟在长期运营中尤为明显。“SenseCAP Watcher Wiki 中心”这个项目正是为了解决这一痛点而生。它不是一个全新的数据采集或存储系统而是一个建立在现有数据流之上的信息聚合与知识管理中枢。其核心目标是为每一台SenseCAP Watcher设备传感器创建一个动态的、结构化的“数字档案”。这个档案不仅包含设备静态信息如型号、序列号、部署位置更深度关联其全生命周期数据实时状态、历史告警、维护记录以及相关的上下文知识部署示意图、现场环境描述、运维SOP。简单来说它旨在将散落在各处的、与特定设备相关的所有信息“缝合”起来形成一个可检索、可协作、可持续更新的单一信息源。这个项目适合所有深度使用SenseCAP生态进行环境监测的团队无论是农业大棚的精细化管理、智慧楼宇的能耗监控还是工业仓库的安防与环境保障。对于运维工程师它是高效的排障手册对于项目经理它是清晰的项目资产地图对于数据分析师它是理解数据背景的宝贵注释库。接下来我将详细拆解这个Wiki中心从设计到落地的完整思路与实操细节。2. 核心设计思路构建设备维度的“数字孪生”知识库2.1 核心理念以设备为中心的信息聚合传统物联网平台的数据视图多以“项目”或“数据类型”为中心。例如你可以查看所有设备的温度曲线或者某个项目下所有传感器的列表。而Watcher Wiki的设计哲学是彻底的“以设备为中心”。每一台SenseCAP Watcher都被视为一个独立的实体围绕这个实体构建多层次的信息图层身份层设备的基础档案包括设备EUI、名称、型号、固件版本、采购日期、保修信息等。这是设备的“身份证”。时空层设备的部署信息包括精确的GPS坐标或室内位置描述、部署时间、部署负责人、以及部署时的现场照片或示意图。这回答了设备“在哪里”和“从何时开始在那里”的问题。数据层与设备实时和历史数据的双向链接。这不是在Wiki里存储数据本身而是通过API接口或嵌入式图表动态展示设备的最新读数、关键指标趋势如最近24小时均值、以及历史告警事件的摘要。这提供了设备的“生命体征”。事件层设备全生命周期的日志包括所有的维护记录如更换电池、清洁传感器、校准记录、异常排查与处理过程、以及任何人为添加的备注。这是设备的“病历本”。知识层与该设备相关的文档、经验、技巧。例如针对该点位光照异常的特殊解读说明附近可能存在的干扰源记录最佳的维护访问路径等。这是附着在设备上的“经验值”。通过这五层的设计一个冰冷的设备ID就转变为一个丰满的、有故事的数字实体。任何团队成员在接触一台设备时都能快速获得其全景信息。2.2 技术选型平衡功能、成本与易用性构建这样一个系统有从简到繁多种技术路径。我们的核心诉求是低成本启动、易于维护、支持协作、具备良好的扩展性。经过对比我选择了基于Wiki.js作为核心平台并搭配一些自动化脚本的方案。为什么是Wiki.js开源与自托管零软件授权成本可以部署在内网服务器或自有云主机上完全掌控数据。现代化与易用性提供直观的图形化编辑器也支持Markdown界面美观学习成本低非技术人员也能轻松贡献内容。强大的结构化能力支持页面标签Tags、目录Hierarchy和自定义属性Properties非常适合为设备创建标准化的档案模板。权限管理精细可以针对不同团队如运维、研发、管理设置不同的页面查看和编辑权限。API友好提供了完善的GraphQL API便于我们从SenseCAP云平台或其他系统自动同步设备状态、告警等信息到Wiki页面实现部分信息的自动化更新。备份与恢复内置备份功能保障知识资产安全。辅助工具链SenseCAP API用于定时拉取设备列表、实时状态、历史告警数据。Python脚本编写自动化同步脚本定期调用SenseCAP API和Wiki.js API更新设备页面的数据摘要。Docker使用Docker Compose部署Wiki.js及其依赖的PostgreSQL数据库极大简化了安装和迁移过程。Nginx作为反向代理提供HTTPS访问和域名绑定。这个方案避免了从零开发一套复杂CMS系统的巨大投入充分利用了成熟开源项目的优势将开发重点聚焦在业务逻辑的集成与自动化上。3. 系统部署与基础架构搭建3.1 服务器环境准备我们选择一台Linux服务器Ubuntu 20.04 LTS进行部署。核心是安装Docker和Docker Compose。# 更新系统包 sudo apt-get update sudo apt-get upgrade -y # 安装Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER newgrp docker # 或重新登录使组生效 # 安装Docker Compose sudo curl -L https://github.com/docker/compose/releases/download/v2.20.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose sudo chmod x /usr/local/bin/docker-compose注意生产环境务必配置防火墙如UFW仅开放必要的端口如80、443、22并为Wiki.js使用非root用户运行Docker以提升安全性。3.2 使用Docker Compose部署Wiki.js创建一个项目目录例如sensecap-wiki并在其中创建docker-compose.yml文件。这里采用PostgreSQL作为数据库更稳定可靠。version: 3 services: db: image: postgres:15-alpine container_name: wiki-js-db environment: POSTGRES_DB: wiki POSTGRES_PASSWORD: your_strong_db_password_here POSTGRES_USER: wikijs volumes: - wiki-db-data:/var/lib/postgresql/data restart: unless-stopped networks: - wiki-network wiki: image: ghcr.io/requarks/wiki:2 container_name: wiki-js depends_on: - db environment: DB_TYPE: postgres DB_HOST: db DB_PORT: 5432 DB_USER: wikijs DB_PASS: your_strong_db_password_here DB_NAME: wiki volumes: - wiki-data:/var/wiki/data - ./wiki-config.yml:/var/wiki/config.yml restart: unless-stopped networks: - wiki-network ports: - 3000:3000 volumes: wiki-db-data: wiki-data: networks: wiki-network: driver: bridge同时创建一个基础的wiki-config.yml配置文件用于设置站点名称、语言等。# wiki-config.yml bindIP: 0.0.0.0 port: 3000 public: true host: localhost # 初始配置后续通过Nginx代理后修改启动服务docker-compose up -d此时访问http://你的服务器IP:3000就能看到Wiki.js的安装向导。按照向导完成管理员账户的创建和基本设置。强烈建议在安装向导中将站点URL设置为你的域名如https://wiki.yourdomain.com即使暂时未配置域名也先预设好避免后续链接错误。3.3 配置Nginx反向代理与HTTPS为了使用域名和HTTPS访问我们配置Nginx。首先安装Nginx和Certbot用于申请Let‘s Encrypt免费SSL证书。sudo apt-get install nginx certbot python3-certbot-nginx -y为Wiki.js创建一个Nginx站点配置文件/etc/nginx/sites-available/wikiserver { listen 80; server_name wiki.yourdomain.com; # 替换为你的域名 client_max_body_size 100M; # 允许上传较大附件如现场图片 location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } }创建符号链接并测试配置sudo ln -s /etc/nginx/sites-available/wiki /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx使用Certbot获取SSL证书sudo certbot --nginx -d wiki.yourdomain.comCertbot会自动修改Nginx配置将其重定向到HTTPS。完成后即可通过https://wiki.yourdomain.com安全访问你的Wiki中心。4. Wiki中心内容结构与模板设计4.1 设计设备页面模板这是整个知识库的核心。我们利用Wiki.js的“编辑器中插入模板”功能创建一个标准化的设备档案模板。在Wiki.js后台管理页面进入“模板”模块创建一个新模板命名为“SenseCAP Watcher Device Template”。模板内容使用Markdown和Wiki.js的自定义属性语法。--- title: {{title}} tags: [device, sensecap, watcher] description: 设备档案 - {{title}} properties: device_eui: device_name: model: firmware: location_gps: location_desc: deployed_at: deployed_by: project: status: active last_data_sync: --- # {{title}} ## 设备概览 | 属性 | 值 | | :--- | :--- | | **设备EUI** | {{properties.device_eui}} | | **设备名称** | {{properties.device_name}} | | **型号** | {{properties.model}} | | **固件版本** | {{properties.firmware}} | | **部署位置** | {{properties.location_desc}} (GPS: {{properties.location_gps}}) | | **部署时间** | {{properties.deployed_at}} | | **部署人** | {{properties.deployed_by}} | | **所属项目** | [[{{properties.project}}]] | | **当前状态** | **{{properties.status}}** | | **最后数据同步** | {{properties.last_data_sync}} | ## 实时数据快照 *此部分由自动化脚本每日更新。最后更新{{properties.last_data_sync}}* **最新读数** - **温度** --.- °C - **湿度** --.- %RH - **大气压** ---- hPa - **光照** ---- Lux - **CO₂** ---- ppm **今日告警**无 *(提示以上为示例实际数据将通过脚本嵌入)* ## 部署与环境信息 ### 现场照片 请上传部署点位的现场照片确保能清晰看到设备安装方式和周围环境 ### 部署示意图 可上传手绘或CAD示意图标注设备具体安装位置、朝向、高度等信息 ### 环境特征与潜在干扰说明 描述点位特殊性如靠近空调出风口、有直接日晒、处于人流密集区等 ## 运维历史记录 | 日期 | 操作类型 | 执行人 | 详情描述 | | :--- | :--- | :--- | :--- | | {{properties.deployed_at}} | 部署 | {{properties.deployed_by}} | 设备初次安装并激活 | | | | | | ## 相关知识链接与备注 - [[项目总览{{properties.project}}]] - [[同型号设备通用维护手册]] - 其他相关页面链接 --- *本页面最后编辑于 {{date}}*这个模板定义了设备页面的基本结构和需要填写的元数据Properties。团队成员创建新设备页面时只需选择此模板然后填写右侧属性栏的表单即可内容区域会自动生成标准化格式。4.2 构建知识库目录结构清晰的目录结构有助于信息导航。建议在Wiki.js中创建如下侧边栏目录- 首页 (站点介绍与使用指南) - 设备档案库 - 按项目分类 - 项目A - [设备A-1] - [设备A-2] - 项目B - 按状态筛选 - 活跃设备 - 故障设备 - 已退役设备 - 运维知识库 - 通用操作手册 (SOP) - 设备部署标准流程 - 电池更换与设备校准指南 - 常见故障排查手册 - 项目特定文档 - 数据分析参考 - 传感器数据解读指南 - 典型场景数据模式 - 管理后台 - (此目录可设置权限仅管理员可见) - 自动化脚本日志 - 数据同步状态利用Wiki.js的“导航”功能可以轻松拖拽页面来构建这个树形菜单。5. 自动化数据同步让Wiki“活”起来静态的设备信息只是基础让实时数据和历史事件自动汇聚到Wiki页面才能使其价值倍增。我们通过Python脚本实现与SenseCAP云平台的定时同步。5.1 获取SenseCAP API凭证登录SenseCAP云平台进入“开发者中心”或“API管理”创建一个新的API密钥API Key并记录下你的Application EUI、Application Key以及Application Secret。这些是调用API的身份凭证。5.2 编写Python同步脚本脚本的核心逻辑是1) 从SenseCAP获取设备列表及状态2) 通过Wiki.js GraphQL API更新对应设备页面的“实时数据快照”部分。首先安装必要的Python库pip install requests schedule创建一个脚本sync_watcher_to_wiki.pyimport requests import json import schedule import time from datetime import datetime # 配置区域 # SenseCAP API 配置 SENSECAP_BASE_URL https://sensecap.seeed.cc/openapi APP_EUI 你的Application_EUI APP_KEY 你的API_Key APP_SECRET 你的API_Secret # Wiki.js GraphQL API 配置 WIKI_GRAPHQL_URL https://wiki.yourdomain.com/graphql # 替换为你的Wiki地址 WIKI_API_KEY 你的Wiki.js_API密钥 # 在Wiki.js后台“API访问”中创建 # 设备EUI到Wiki页面路径的映射可先从Wiki导出页面列表生成基础映射后续动态更新 DEVICE_PAGE_MAP { 2CF7F1C044000001: /device/warehouse-temp-01, 2CF7F1C044000002: /device/warehouse-humidity-01, # ... 添加更多设备映射 } # 配置结束 def get_sensecap_token(): 获取SenseCAP API访问令牌 url f{SENSECAP_BASE_URL}/get_token payload { app_eui: APP_EUI, app_key: APP_KEY, app_secret: APP_SECRET } try: resp requests.post(url, jsonpayload, timeout10) resp.raise_for_status() token_data resp.json() return token_data.get(data, {}).get(access_token) except requests.exceptions.RequestException as e: print(f[Error] 获取SenseCAP Token失败: {e}) return None def get_device_latest_data(device_eui, access_token): 获取指定设备的最新数据 url f{SENSECAP_BASE_URL}/view_device_latest_data?device_eui{device_eui} headers {Authorization: fBearer {access_token}} try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp.json().get(data, []) except requests.exceptions.RequestException as e: print(f[Error] 获取设备 {device_eui} 数据失败: {e}) return [] def update_wiki_page(page_path, content_update, summaryAuto-sync device data): 通过GraphQL更新Wiki页面内容 # 首先查询页面ID和当前内容 query_id query { pages { list(path: %s) { id content hash } } } % page_path headers { Authorization: fBearer {WIKI_API_KEY}, Content-Type: application/json } try: # 1. 获取页面信息 resp requests.post(WIKI_GRAPHQL_URL, json{query: query_id}, headersheaders, timeout15) resp.raise_for_status() page_data resp.json().get(data, {}).get(pages, {}).get(list, []) if not page_data: print(f[Warning] 未找到页面: {page_path}) return False page_info page_data[0] page_id page_info[id] current_hash page_info[hash] # 2. 更新页面 mutation mutation { pages { update( id: %d, content: %s, hash: %s, description: %s, isPublished: true ) { responseResult { succeeded errorCode slug message } page { id path title } } } } % (page_id, content_update.replace(, \\).replace(\n, \\n), current_hash, summary) resp requests.post(WIKI_GRAPHQL_URL, json{query: mutation}, headersheaders, timeout15) resp.raise_for_status() result resp.json() if result.get(data, {}).get(pages, {}).get(update, {}).get(responseResult, {}).get(succeeded): print(f[Success] 页面 {page_path} 更新成功) return True else: error_msg result.get(data, {}).get(pages, {}).get(update, {}).get(responseResult, {}) print(f[Error] 页面更新失败: {error_msg}) return False except requests.exceptions.RequestException as e: print(f[Error] 更新Wiki页面 {page_path} 时网络错误: {e}) return False except Exception as e: print(f[Error] 更新Wiki页面 {page_path} 时发生未知错误: {e}) return False def generate_data_section(device_data): 根据设备数据生成Markdown格式的‘实时数据快照’部分 if not device_data: return **暂无最新数据**\n\n*(设备可能离线或暂无上报)* lines [**最新读数**] # 假设数据以列表形式返回每个元素是一个测量值 for item in device_data: measurement item.get(measurement_name, ) value item.get(measurement_value, ) unit item.get(measurement_unit, ) if measurement and value is not None: # 将常见的measurement_name映射为中文 name_map { temperature: 温度, humidity: 湿度, barometric_pressure: 大气压, light_intensity: 光照, co2_concentration: CO₂ } display_name name_map.get(measurement, measurement) lines.append(f- **{display_name}** {value} {unit}) lines.append(\n**今日告警**无 *(此功能需调用告警API此处为示例)*) return \n.join(lines) def sync_job(): 定时同步任务主函数 print(f[{datetime.now().isoformat()}] 开始同步任务...) token get_sensecap_token() if not token: return sync_time datetime.now().strftime(%Y-%m-%d %H:%M:%S) for device_eui, page_path in DEVICE_PAGE_MAP.items(): print(f 处理设备: {device_eui} - {page_path}) # 1. 从SenseCAP获取数据 latest_data get_device_latest_data(device_eui, token) # 2. 生成更新的内容片段 new_data_section generate_data_section(latest_data) # 3. 这里需要更智能地更新页面先获取整个页面内容然后替换“实时数据快照”部分。 # 由于Wiki.js API更新需要完整内容一个更稳健的做法是 # a. 获取页面完整Markdown # b. 使用正则表达式或标记定位到“## 实时数据快照”部分并进行替换 # c. 更新properties中的last_data_sync字段 # 此处为简化示例假设我们只更新一个特定区域。实际实现会更复杂。 update_content f## 实时数据快照\n *此部分由自动化脚本每日更新。最后更新{sync_time}*\n\n{new_data_section} # 4. 调用函数更新Wiki页面此处需要更精细的内容合并逻辑略 # update_wiki_page(page_path, update_content, summaryf数据同步 {sync_time}) print(f 生成更新内容预览前100字符: {update_content[:100]}...) print(f[{datetime.now().isoformat()}] 同步任务结束。) if __name__ __main__: # 立即执行一次 sync_job() # schedule.every(1).hours.do(sync_job) # 每1小时执行一次 schedule.every(30).minutes.do(sync_job) # 每30分钟执行一次测试用 print(定时同步任务已启动按 CtrlC 退出。) while True: schedule.run_pending() time.sleep(60)重要提示上述脚本中的update_wiki_page函数是一个简化示例。在实际生产中直接替换整个页面内容会覆盖人工编辑的部分这是不可接受的。正确的做法是在设备页面模板的“实时数据快照”部分使用特殊的HTML注释作为标记例如!-- AUTO-DATA-START --和!-- AUTO-DATA-END --。同步脚本在获取页面完整内容后使用正则表达式精准定位这两个标记之间的内容进行替换。同时通过Wiki.js GraphQL API的updatePage变异操作只更新content字段而保留properties等其他字段不变。更新properties中的last_data_sync则需要调用专门的updatePagePropertiesAPI。 这是一个需要仔细处理的关键细节确保自动化与人工编辑和谐共存。5.3 部署与运行同步脚本将脚本放在服务器上使用systemd或supervisor将其作为后台服务运行确保其稳定性和开机自启。# 使用systemd示例 sudo nano /etc/systemd/system/sensecap-wiki-sync.service服务文件内容[Unit] DescriptionSenseCAP Watcher Wiki Sync Service Afternetwork.target [Service] Typesimple Useryour_username WorkingDirectory/path/to/your/script ExecStart/usr/bin/python3 /path/to/your/script/sync_watcher_to_wiki.py Restarton-failure RestartSec10 [Install] WantedBymulti-user.target然后启动并启用服务sudo systemctl daemon-reload sudo systemctl start sensecap-wiki-sync sudo systemctl enable sensecap-wiki-sync sudo systemctl status sensecap-wiki-sync # 查看状态6. 高级功能与最佳实践6.1 利用Webhook实现事件驱动更新除了定时轮询更高效的更新方式是事件驱动。SenseCAP平台支持配置Webhook当设备发生上下线、触发告警、或数据异常时可以主动向一个URL发送POST请求。我们可以在服务器上运行一个简单的Webhook接收服务例如用Flask或FastAPI编写当收到SenseCAP的Webhook通知时解析出相关的设备EUI和事件类型然后立即触发对该设备Wiki页面的更新或者在页面中新增一条“事件记录”。这能极大提升Wiki信息的时效性。6.2 建立设备-页面自动映射手动维护DEVICE_PAGE_MAP字典是繁琐的。我们可以改进脚本实现自动映射在Wiki.js中规定设备页面的路径必须包含设备EUI例如/device/仓库-温湿度-2CF7F1C044000001。同步脚本首先调用Wiki.js API获取所有标签为“device”的页面列表。从页面路径或页面属性中解析出设备EUI。建立内存中的映射关系用于后续的数据同步。 这样每当在Wiki中新建一个设备页面只要遵循命名规范它就会自动被纳入同步范围。6.3 版本控制与变更历史Wiki.js内置了完整的页面版本历史功能。每一次编辑无论是人工还是API调用都会创建一个新版本。这对于运维记录至关重要。当设备出现故障时可以回溯查看“运维历史记录”表格是谁在什么时候修改的或者“现场照片”是否被更新过便于追根溯源。6.4 权限管理与团队协作根据团队角色设置权限管理员拥有全部权限负责系统维护、模板设计、用户管理。运维工程师可以创建、编辑所有设备页面上传照片更新运维记录。项目成员可以查看和编辑自己所负责项目的设备页面。访客只能查看公开页面如一些通用的操作手册。在Wiki.js的“用户与群组”设置中精细配置确保信息安全和权责清晰。7. 常见问题与排查技巧实录7.1 Wiki.js部署后无法访问现象Docker容器运行正常但无法通过IP:3000访问。排查检查服务器防火墙是否放行了3000端口sudo ufw status。检查Docker容器日志docker logs wiki-js查看是否有启动错误。进入容器内部检查docker exec -it wiki-js sh然后curl localhost:3000判断服务在容器内是否正常。最常见的原因是安装向导中设置的“站点URL”与实际访问地址不匹配。需要进入Wiki.js的数据库或通过安装时创建的config.yml文件进行修改。7.2 自动化脚本无法更新Wiki页面现象脚本运行无报错但Wiki页面内容未变。排查检查API密钥确认Wiki.js API密钥具有“管理页面”的权限。检查页面路径确保DEVICE_PAGE_MAP或自动解析出的路径是Wiki.js中的完整路径如/device/xxx且大小写敏感。查看脚本日志脚本应打印详细的成功/失败信息。关注GraphQL返回的responseResult中的错误信息。手动测试GraphQL使用Postman或curl工具手动发送一个简单的查询请求验证API连通性和权限。内容合并冲突这是最隐蔽的问题。确保你的内容替换逻辑不会意外删除人工编辑的内容。使用HTML注释标记法是最稳妥的。7.3 SenseCAP API调用返回错误现象脚本日志显示获取Token或设备数据失败。排查验证凭证确认APP_EUI,APP_KEY,APP_SECRET无误且在SenseCAP平台未过期或被禁用。检查网络确保服务器可以访问https://sensecap.seeed.cc。查看API限制SenseCAP API可能有调用频率限制。如果脚本运行太频繁可能会被暂时限制。在代码中加入适当的延时和错误重试机制。设备EUI是否正确确认你要查询的设备EUI确实存在于你的SenseCAP账户下。7.4 团队使用积极性不高现象Wiki建好了但只有少数人在维护逐渐变成另一个信息孤岛。解决自上而下推动将更新Wiki作为运维流程的强制环节。例如规定每次现场维护后必须在对应设备页面更新“运维历史记录”并上传照片才能关闭工单。降低使用门槛通过培训展示Wiki如何快速帮助他们解决问题。例如在新人入职时直接引导他通过Wiki了解设备而不是去问老员工。让工具“有用”确保自动化同步的数据准确、及时。当团队成员发现打开Wiki就能看到最新设备状态而不是需要登录另一个平台时他们自然会依赖它。设立维护榜样定期表扬和维护得好的页面在团队内部分享Wiki解决实际问题的案例。构建“SenseCAP Watcher Wiki中心”并非一蹴而就它是一个需要持续运营和优化的过程。启动初期可以从一个核心项目、十几台关键设备开始试点跑通从信息录入、自动化同步到实际使用的完整闭环。当团队尝到“信息随手可得”的甜头后再逐步推广到所有项目和设备。这个中心最终会成为你们物联网资产中最有价值的知识沉淀让每一台沉默的传感器都能讲述它自己的故事。