deviceController.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. const pool = require('../config/db');
  2. const logger = require('../logger'); // 引入日志记录器
  3. const mqtt = require('../mqttClient'); // 从根目录引入
  4. const client = mqtt.client; // 根据你的导出方式调整
  5. // 新增设备查询方法
  6. getDevices: async (req, res) => {
  7. try {
  8. const fields = req.query.fields || 'device_id,name,model,system_version,status'
  9. const [results] = await pool.promise().query(`
  10. SELECT ${fields}
  11. FROM devices
  12. WHERE system_version IS NOT NULL
  13. `)
  14. res.json(results)
  15. } catch (error) {
  16. logger.error('获取设备列表失败:', error)
  17. res.status(500).json({ code: 500, message: '服务器内部错误' })
  18. }
  19. }
  20. const getDevices = (req, res) => {
  21. logger.info('开始获取所有设备信息');
  22. const query = `
  23. SELECT
  24. d.id, d.device_id, d.room_id, r.room_name, d.status, d.last_seen, d.name,
  25. d.temperature, d.upload_time, d.switch_status, d.switch_status_time,
  26. d.level_status, d.level_status_time, d.bound_device_id, d.bound_time,
  27. d.first_online_time, d.last_online_time, d.last_offline_time, d.ip_address
  28. FROM devices d
  29. LEFT JOIN rooms r ON d.room_id = r.id
  30. `;
  31. pool.query(query, (err, results) => {
  32. if (err) {
  33. logger.error('查询所有设备失败:', err);
  34. return res.status(500).send('Error querying devices');
  35. }
  36. logger.info(`成功获取 ${results.length} 个设备信息`);
  37. res.status(200).json(results);
  38. });
  39. };
  40. // 绑定设备到房间
  41. const bindDeviceToRoom = (req, res) => {
  42. const { deviceId, roomId } = req.body;
  43. logger.info(`尝试绑定设备 ${deviceId} 到房间 ${roomId}`);
  44. if (!deviceId || !roomId) {
  45. logger.warn('绑定设备失败:设备 ID 和房间 ID 不能为空');
  46. return res.status(400).json({ success: false, message: '设备 ID 和房间 ID 不能为空' });
  47. }
  48. const isRelayDevice = deviceId.includes('ESP32');
  49. if (isRelayDevice) {
  50. // 检查房间是否已经绑定了继电器
  51. const checkRelayQuery = `
  52. SELECT device_id
  53. FROM devices
  54. WHERE room_id = ? AND device_id LIKE '%ESP32%'
  55. `;
  56. pool.query(checkRelayQuery, [roomId], (err, results) => {
  57. if (err) {
  58. logger.error('检查继电器设备失败:', err);
  59. return res.status(500).json({ success: false, message: '检查继电器设备时出错' });
  60. }
  61. if (results.length > 0) {
  62. logger.warn(`绑定设备失败:房间 ${roomId} 已经绑定了继电器`);
  63. return res.status(400).json({ success: false, message: '该房间已经绑定了继电器,无法再次绑定' });
  64. }
  65. // 执行绑定操作
  66. executeBinding(deviceId, roomId, res);
  67. });
  68. } else {
  69. // 非继电器设备直接执行绑定
  70. executeBinding(deviceId, roomId, res);
  71. }
  72. };
  73. // 辅助函数:执行设备绑定
  74. const executeBinding = (deviceId, roomId, res) => {
  75. // 首先获取房间名称
  76. const getRoomNameQuery = 'SELECT room_name FROM rooms WHERE id = ?';
  77. pool.query(getRoomNameQuery, [roomId], (err, roomResults) => {
  78. if (err) {
  79. logger.error('获取房间名称失败:', err);
  80. return res.status(500).json({ success: false, message: '获取房间名称时出错' });
  81. }
  82. if (roomResults.length === 0) {
  83. logger.warn(`房间 ${roomId} 不存在`);
  84. return res.status(404).json({ success: false, message: '房间不存在' });
  85. }
  86. const roomName = roomResults[0].room_name;
  87. // 更新设备表中的 room_id 和 room_name
  88. const bindQuery = 'UPDATE devices SET room_id = ?, room_name = ? WHERE device_id = ?';
  89. pool.query(bindQuery, [roomId, roomName, deviceId], (err, results) => {
  90. if (err) {
  91. logger.error('绑定设备失败:', err);
  92. return res.status(500).json({ success: false, message: '绑定设备时出错' });
  93. }
  94. if (results.affectedRows === 0) {
  95. logger.warn(`设备 ${deviceId} 不存在或已绑定到相同房间`);
  96. return res.status(404).json({ success: false, message: '设备不存在或已绑定到相同房间' });
  97. }
  98. logger.info(`设备 ${deviceId} 成功绑定到房间 ${roomId}`);
  99. res.status(200).json({ success: true, message: '设备绑定成功' });
  100. });
  101. });
  102. };
  103. // 解绑设备
  104. const unbindDevice = (req, res) => {
  105. const { deviceId } = req.params;
  106. logger.info(`尝试解绑设备: ${deviceId}`);
  107. const query = 'UPDATE devices SET room_id = NULL, room_name = NULL WHERE device_id = ?';
  108. pool.query(query, [deviceId], (err, results) => {
  109. if (err) {
  110. logger.error('解绑设备失败:', err);
  111. return res.status(500).send('Error unbinding device');
  112. }
  113. logger.info(`设备 ${deviceId} 解绑成功`);
  114. res.status(200).send('设备已从房间中移除');
  115. });
  116. };
  117. // 删除设备
  118. const deleteDevice = (req, res) => {
  119. const { deviceId } = req.params;
  120. logger.info(`尝试删除设备: ${deviceId}`);
  121. const query = 'DELETE FROM devices WHERE device_id = ?';
  122. pool.query(query, [deviceId], (err, results) => {
  123. if (err) {
  124. logger.error('删除设备失败:', err);
  125. return res.status(500).send('Error deleting device');
  126. }
  127. logger.info(`设备 ${deviceId} 删除成功`);
  128. res.status(200).send('设备删除成功');
  129. });
  130. };
  131. // 更新设备信息
  132. const updateDevice = async (req, res) => {
  133. const { deviceId, name, roomId } = req.body;
  134. logger.info(`尝试更新设备信息: deviceId=${deviceId}, name=${name}, roomId=${roomId}`);
  135. try {
  136. // 检查设备类型
  137. const deviceQuery = 'SELECT device_id FROM devices WHERE device_id = ?';
  138. const [deviceResults] = await pool.promise().query(deviceQuery, [deviceId]);
  139. if (deviceResults.length === 0) {
  140. logger.warn(`设备 ${deviceId} 不存在`);
  141. return res.status(404).json({ success: false, message: '设备不存在' });
  142. }
  143. // 如果是继电器设备,检查目标房间是否已有其他继电器
  144. if (deviceId.includes('ESP32')) {
  145. const relayQuery = `
  146. SELECT device_id
  147. FROM devices
  148. WHERE room_id = ? AND device_id LIKE "%ESP32%" AND device_id != ?
  149. `;
  150. const [relayResults] = await pool.promise().query(relayQuery, [roomId, deviceId]);
  151. if (relayResults.length > 0) {
  152. logger.warn(`房间 ${roomId} 已绑定其他继电器`);
  153. return res.status(400).json({ success: false, message: '该房间已绑定其他继电器' });
  154. }
  155. }
  156. // 获取房间名称
  157. let roomName = null;
  158. if (roomId) {
  159. const roomQuery = 'SELECT room_name FROM rooms WHERE id = ?';
  160. const [roomResults] = await pool.promise().query(roomQuery, [roomId]);
  161. if (roomResults.length > 0) {
  162. roomName = roomResults[0].room_name;
  163. } else {
  164. logger.warn(`房间 ${roomId} 不存在`);
  165. return res.status(404).json({ success: false, message: '房间不存在' });
  166. }
  167. }
  168. // 更新设备信息,包括房间名称
  169. const updateQuery = 'UPDATE devices SET name = ?, room_id = ?, room_name = ? WHERE device_id = ?';
  170. await pool.promise().query(updateQuery, [name, roomId, roomName, deviceId]);
  171. logger.info(`成功更新设备信息: deviceId=${deviceId}, roomName=${roomName}`);
  172. res.status(200).json({ success: true, message: '设备信息更新成功' });
  173. } catch (err) {
  174. logger.error('更新设备信息失败:', err);
  175. res.status(500).json({ success: false, message: '更新设备信息失败' });
  176. }
  177. };
  178. // 获取设备状态
  179. const getDeviceStatus = (req, res) => {
  180. logger.info('开始获取设备状态信息');
  181. const query = `
  182. SELECT
  183. d.device_id,
  184. d.status AS device_status,
  185. rs.state AS relay_state,
  186. rs.temperature,
  187. rs.timestamp
  188. FROM devices d
  189. LEFT JOIN relay_state rs ON d.device_id = rs.device_id
  190. ORDER BY rs.timestamp DESC
  191. `;
  192. pool.query(query, (err, results) => {
  193. if (err) {
  194. logger.error('查询设备状态失败:', err);
  195. return res.status(500).send('Error querying device status');
  196. }
  197. logger.info(`成功获取 ${results.length} 个设备的状态信息`);
  198. res.status(200).json(results);
  199. });
  200. };
  201. // 设备管理界面查询绑定状态
  202. const getDevicesByRoom = (req, res) => {
  203. const { roomId } = req.query;
  204. logger.info(`开始获取房间 ${roomId} 的设备信息`);
  205. if (!roomId) {
  206. logger.warn('获取设备失败:房间ID为空');
  207. return res.status(400).send('房间 ID 不能为空');
  208. }
  209. const query = `
  210. SELECT
  211. device_id,
  212. name,
  213. status,
  214. temperature,
  215. switch_status,
  216. level_status
  217. FROM devices
  218. WHERE room_id = ?
  219. `;
  220. pool.query(query, [roomId], (err, results) => {
  221. if (err) {
  222. logger.error(`获取房间 ${roomId} 的设备失败:`, err);
  223. return res.status(500).send('Error querying devices by room');
  224. }
  225. logger.info(`成功获取房间 ${roomId} 的 ${results.length} 个设备信息`);
  226. res.status(200).json(results);
  227. });
  228. };
  229. // 设备管理在线状态
  230. const getDeviceStatusByRoom = (req, res) => {
  231. const { roomId } = req.query;
  232. logger.info(`开始获取房间 ${roomId} 的设备状态`);
  233. if (!roomId) {
  234. logger.warn('获取设备状态失败:房间ID为空');
  235. return res.status(400).send('房间 ID 不能为空');
  236. }
  237. const query = `
  238. SELECT
  239. d.device_id,
  240. d.status AS device_status,
  241. rs.state AS relay_state,
  242. rs.temperature,
  243. rs.timestamp
  244. FROM devices d
  245. LEFT JOIN relay_state rs ON d.device_id = rs.device_id
  246. WHERE d.room_id = ?
  247. ORDER BY rs.timestamp DESC
  248. `;
  249. pool.query(query, [roomId], (err, results) => {
  250. if (err) {
  251. logger.error(`获取房间 ${roomId} 的设备状态失败:`, err);
  252. return res.status(500).send('Error querying device status');
  253. }
  254. logger.info(`成功获取房间 ${roomId} 的 ${results.length} 个设备状态`);
  255. res.status(200).json(results);
  256. });
  257. };
  258. // 获取 GPIO 状态
  259. const getGpioState = (req, res) => {
  260. const { deviceId } = req.query;
  261. logger.info(`开始获取设备 ${deviceId} 的GPIO状态`);
  262. if (!deviceId) {
  263. logger.warn('获取GPIO状态失败:设备ID为空');
  264. return res.status(400).send('设备 ID 不能为空');
  265. }
  266. const query = `
  267. SELECT state
  268. FROM gpio_state
  269. WHERE device_id = ?
  270. ORDER BY timestamp DESC
  271. LIMIT 1
  272. `;
  273. pool.query(query, [deviceId], (err, results) => {
  274. if (err) {
  275. logger.error(`获取设备 ${deviceId} 的GPIO状态失败:`, err);
  276. return res.status(500).send('Error querying GPIO state');
  277. }
  278. logger.info(`成功获取设备 ${deviceId} 的GPIO状态`);
  279. res.status(200).json(results[0] || { state: 'low' });
  280. });
  281. };
  282. // 绑定继电器到人体传感器
  283. const bindRelayToSensor = async (req, res) => {
  284. const { sensorId, roomId } = req.body;
  285. logger.info(`尝试绑定房间 ${roomId} 的继电器到传感器 ${sensorId}`);
  286. if (!sensorId || !roomId) {
  287. logger.warn('绑定失败:传感器ID或房间ID为空');
  288. return res.status(400).json({ success: false, message: '传感器ID和房间ID不能为空' });
  289. }
  290. try {
  291. // 查询房间中的继电器设备
  292. const relayQuery = `
  293. SELECT device_id
  294. FROM devices
  295. WHERE room_id = ? AND device_id LIKE '%ESP32%'
  296. `;
  297. const [relayResults] = await pool.promise().query(relayQuery, [roomId]);
  298. if (relayResults.length === 0) {
  299. logger.warn(`房间 ${roomId} 没有继电器设备`);
  300. return res.status(404).json({ success: false, message: '该房间没有继电器设备' });
  301. }
  302. const relayId = relayResults[0].device_id;
  303. logger.info(`找到继电器设备: ${relayId}`);
  304. // 更新人体传感器模块的 bound_device_id 和 bound_time
  305. const updateQuery = `
  306. UPDATE devices
  307. SET bound_device_id = ?, bound_time = NOW()
  308. WHERE device_id = ?
  309. `;
  310. await pool.promise().query(updateQuery, [relayId, sensorId]);
  311. // 通过 MQTT 发送继电器 ID
  312. const controlTopic = `device/${sensorId}/control`;
  313. client.publish(controlTopic, relayId, { qos: 1 }, (err) => { // 添加了 qos: 1 确保消息可靠传递
  314. if (err) {
  315. logger.error('MQTT消息发送失败:', err);
  316. return res.status(500).json({ success: false, message: 'MQTT 消息发布失败' });
  317. }
  318. logger.info(`成功绑定并配置继电器: 传感器=${sensorId}, 继电器=${relayId}`);
  319. res.status(200).json({
  320. success: true,
  321. message: '继电器与传感器绑定成功,并已发送配置信息'
  322. });
  323. });
  324. } catch (error) {
  325. logger.error('绑定继电器到传感器失败:', error);
  326. res.status(500).json({ success: false, message: '处理绑定请求时出错' });
  327. }
  328. };
  329. // 新增:获取所有房间及其设备信息
  330. const getAllRoomsWithDevices = async (req, res) => {
  331. logger.info('开始获取所有房间及其设备信息');
  332. const query = `
  333. SELECT
  334. r.id AS room_id,
  335. r.room_name,
  336. r.orientation,
  337. d.id AS device_id,
  338. d.device_id AS device_uid,
  339. d.status,
  340. d.temperature,
  341. d.switch_status,
  342. d.level_status
  343. FROM rooms r
  344. LEFT JOIN devices d ON r.id = d.room_id
  345. ORDER BY r.id
  346. `;
  347. pool.query(query, (err, results) => {
  348. if (err) {
  349. logger.error('获取房间和设备信息失败:', err);
  350. return res.status(500).send('Error querying rooms and devices');
  351. }
  352. // 将结果按房间分组
  353. const roomsMap = new Map();
  354. results.forEach(row => {
  355. if (!roomsMap.has(row.room_id)) {
  356. roomsMap.set(row.room_id, {
  357. id: row.room_id,
  358. room_name: row.room_name,
  359. orientation: row.orientation,
  360. devices: []
  361. });
  362. }
  363. if (row.device_id) {
  364. roomsMap.get(row.room_id).devices.push({
  365. device_id: row.device_uid,
  366. status: row.status,
  367. temperature: row.temperature,
  368. switch_status: row.switch_status,
  369. level_status: row.level_status
  370. });
  371. }
  372. });
  373. const rooms = Array.from(roomsMap.values());
  374. logger.info(`成功获取 ${rooms.length} 个房间及其设备信息`);
  375. res.status(200).json(rooms);
  376. });
  377. };
  378. module.exports = {
  379. getDevices,
  380. bindDeviceToRoom,
  381. unbindDevice,
  382. deleteDevice,
  383. updateDevice,
  384. getDeviceStatus,
  385. getDeviceStatusByRoom,
  386. getDevicesByRoom,
  387. getGpioState,
  388. bindRelayToSensor,
  389. getAllRoomsWithDevices
  390. };