├── DevicesParser ├── PhicommAirDetectorBaseParser.js └── PhicommAirDetectorM1Parser.js ├── README.md ├── images └── M1.jpg ├── index.js ├── lib ├── AccessoryUtil.js ├── ConfigUtil.js └── LogUtil.js └── package.json /DevicesParser/PhicommAirDetectorBaseParser.js: -------------------------------------------------------------------------------- 1 | class PhicommAirDetectorBaseParser { 2 | constructor(platform) { 3 | this.platform = platform; 4 | } 5 | } 6 | 7 | module.exports = PhicommAirDetectorBaseParser; 8 | -------------------------------------------------------------------------------- /DevicesParser/PhicommAirDetectorM1Parser.js: -------------------------------------------------------------------------------- 1 | const PhicommAirDetectorBaseParser = require('./PhicommAirDetectorBaseParser'); 2 | 3 | var Accessory, PlatformAccessory, Service, Characteristic, UUIDGen; 4 | 5 | class PhicommAirDetectorM1Parser extends PhicommAirDetectorBaseParser { 6 | constructor(platform) { 7 | super(platform); 8 | 9 | Accessory = platform.Accessory; 10 | PlatformAccessory = platform.PlatformAccessory; 11 | Service = platform.Service; 12 | Characteristic = platform.Characteristic; 13 | UUIDGen = platform.UUIDGen; 14 | } 15 | 16 | parser(deviceMacStr, dataStr) { 17 | var that = this; 18 | 19 | that.parserTemperatureAccessory(deviceMacStr, dataStr); 20 | that.parserHumidityAccessory(deviceMacStr, dataStr); 21 | that.parserAirQualityAccessory(deviceMacStr, dataStr); 22 | that.parserLightbulbAccessory(deviceMacStr, dataStr); 23 | } 24 | 25 | parserTemperatureAccessory(deviceMacStr, dataStr) { 26 | var that = this; 27 | 28 | var accessoryName = that.platform.ConfigUtil.getDeviceName(deviceMacStr, 'temperatureName'); 29 | var uuid = UUIDGen.generate('PhicommAirDetectorPlatform_' + deviceMacStr + '_Temperature'); 30 | var accessory = that.platform.AccessoryUtil.getByUUID(uuid); 31 | if(null == accessory) { 32 | accessory = new PlatformAccessory(accessoryName, uuid, Accessory.Categories.SENSOR); 33 | accessory.getService(Service.AccessoryInformation) 34 | .setCharacteristic(Characteristic.Manufacturer, "Phicomm") 35 | .setCharacteristic(Characteristic.Model, "M1") 36 | .setCharacteristic(Characteristic.SerialNumber, deviceMacStr); 37 | accessory.addService(Service.TemperatureSensor, accessoryName); 38 | that.platform.api.registerPlatformAccessories('homebridge-phicomm-air_detector', 'PhicommAirDetectorPlatform', [accessory]); 39 | that.platform.AccessoryUtil.add(accessory); 40 | } 41 | 42 | var service = accessory.getService(Service.TemperatureSensor); 43 | var currentTemperatureCharacteristic = service.getCharacteristic(Characteristic.CurrentTemperature); 44 | 45 | var dataObj = JSON.parse(dataStr); 46 | currentTemperatureCharacteristic.updateValue(dataObj['temperature']); 47 | 48 | if(currentTemperatureCharacteristic.listeners('get').length == 0) { 49 | currentTemperatureCharacteristic.on("get", function(callback) { 50 | callback(null, currentTemperatureCharacteristic.value); 51 | }); 52 | } 53 | } 54 | 55 | parserHumidityAccessory(deviceMacStr, dataStr) { 56 | var that = this; 57 | 58 | var accessoryName = that.platform.ConfigUtil.getDeviceName(deviceMacStr, 'humidityName'); 59 | var uuid = UUIDGen.generate('PhicommAirDetectorPlatform_' + deviceMacStr + '_Humidity'); 60 | var accessory = that.platform.AccessoryUtil.getByUUID(uuid); 61 | if(null == accessory) { 62 | accessory = new PlatformAccessory(accessoryName, uuid, Accessory.Categories.SENSOR); 63 | accessory.getService(Service.AccessoryInformation) 64 | .setCharacteristic(Characteristic.Manufacturer, "Phicomm") 65 | .setCharacteristic(Characteristic.Model, "M1") 66 | .setCharacteristic(Characteristic.SerialNumber, deviceMacStr); 67 | accessory.addService(Service.HumiditySensor, accessoryName); 68 | that.platform.api.registerPlatformAccessories('homebridge-phicomm-air_detector', 'PhicommAirDetectorPlatform', [accessory]); 69 | that.platform.AccessoryUtil.add(accessory); 70 | } 71 | 72 | var service = accessory.getService(Service.HumiditySensor); 73 | var currentRelativeHumidityCharacteristic = service.getCharacteristic(Characteristic.CurrentRelativeHumidity); 74 | 75 | var dataObj = JSON.parse(dataStr); 76 | currentRelativeHumidityCharacteristic.updateValue(dataObj['humidity']); 77 | 78 | if(currentRelativeHumidityCharacteristic.listeners('get').length == 0) { 79 | currentRelativeHumidityCharacteristic.on("get", function(callback) { 80 | callback(null, currentRelativeHumidityCharacteristic.value); 81 | }); 82 | } 83 | } 84 | 85 | parserAirQualityAccessory(deviceMacStr, dataStr) { 86 | var that = this; 87 | 88 | var accessoryName = that.platform.ConfigUtil.getDeviceName(deviceMacStr, 'airQualityName'); 89 | var uuid = UUIDGen.generate('PhicommAirDetectorPlatform_' + deviceMacStr + '_AirQualitySensor'); 90 | var accessory = that.platform.AccessoryUtil.getByUUID(uuid); 91 | if(null == accessory) { 92 | accessory = new PlatformAccessory(accessoryName, uuid, Accessory.Categories.SENSOR); 93 | accessory.getService(Service.AccessoryInformation) 94 | .setCharacteristic(Characteristic.Manufacturer, "Phicomm") 95 | .setCharacteristic(Characteristic.Model, "M1") 96 | .setCharacteristic(Characteristic.SerialNumber, deviceMacStr); 97 | var service = accessory.addService(Service.AirQualitySensor, accessoryName); 98 | service.addCharacteristic(Characteristic.PM2_5Density); 99 | service.addCharacteristic(Characteristic.VOCDensity); 100 | that.platform.api.registerPlatformAccessories('homebridge-phicomm-air_detector', 'PhicommAirDetectorPlatform', [accessory]); 101 | that.platform.AccessoryUtil.add(accessory); 102 | } 103 | 104 | var service = accessory.getService(Service.AirQualitySensor); 105 | var airQualityCharacteristic = service.getCharacteristic(Characteristic.AirQuality) 106 | var pm2_5Characteristic = service.getCharacteristic(Characteristic.PM2_5Density); 107 | var vocCharacteristic = service.getCharacteristic(Characteristic.VOCDensity); 108 | 109 | var dataObj = JSON.parse(dataStr); 110 | pm2_5Characteristic.updateValue(dataObj['value']); 111 | vocCharacteristic.updateValue(dataObj['hcho']); 112 | if(pm2_5Characteristic.value <= 50) { 113 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.EXCELLENT); 114 | } else if(pm2_5Characteristic.value > 50 && pm2_5Characteristic.value <= 100) { 115 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.GOOD); 116 | } else if(pm2_5Characteristic.value > 100 && pm2_5Characteristic.value <= 200) { 117 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.FAIR); 118 | } else if(pm2_5Characteristic.value > 200 && pm2_5Characteristic.value <= 300) { 119 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.INFERIOR); 120 | } else if(pm2_5Characteristic.value > 300) { 121 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.POOR); 122 | } else { 123 | airQualityCharacteristic.updateValue(Characteristic.AirQuality.UNKNOWN); 124 | } 125 | 126 | if(pm2_5Characteristic.listeners('get').length == 0) { 127 | pm2_5Characteristic.on("get", function(callback) { 128 | callback(null, pm2_5Characteristic.value); 129 | }); 130 | } 131 | 132 | if(vocCharacteristic.listeners('get').length == 0) { 133 | vocCharacteristic.on("get", function(callback) { 134 | callback(null, vocCharacteristic.value); 135 | }); 136 | } 137 | } 138 | 139 | parserLightbulbAccessory(deviceMacStr, dataStr) { 140 | var that = this; 141 | 142 | var accessoryName = that.platform.ConfigUtil.getDeviceName(deviceMacStr, 'ledBulbName'); 143 | var uuid = UUIDGen.generate('PhicommAirDetectorPlatform_' + deviceMacStr + '_Lightbulb'); 144 | var accessory = that.platform.AccessoryUtil.getByUUID(uuid); 145 | if(null == accessory) { 146 | accessory = new PlatformAccessory(accessoryName, uuid, Accessory.Categories.LIGHTBULB); 147 | accessory.getService(Service.AccessoryInformation) 148 | .setCharacteristic(Characteristic.Manufacturer, "Phicomm") 149 | .setCharacteristic(Characteristic.Model, "M1") 150 | .setCharacteristic(Characteristic.SerialNumber, deviceMacStr); 151 | var service = accessory.addService(Service.Lightbulb, accessoryName); 152 | service.addCharacteristic(Characteristic.Brightness); 153 | that.platform.api.registerPlatformAccessories('homebridge-phicomm-air_detector', 'PhicommAirDetectorPlatform', [accessory]); 154 | that.platform.AccessoryUtil.add(accessory); 155 | } 156 | 157 | var service = accessory.getService(Service.Lightbulb); 158 | var switchCharacteristic = service.getCharacteristic(Characteristic.On); 159 | var brightnessCharacteristic = service.getCharacteristic(Characteristic.Brightness); 160 | 161 | var dataObj = JSON.parse(dataStr); 162 | switchCharacteristic.updateValue(true); 163 | brightnessCharacteristic.updateValue(100); 164 | 165 | if(switchCharacteristic.listeners('get').length == 0) { 166 | switchCharacteristic.on("get", function(callback) { 167 | callback(null, switchCharacteristic.value); 168 | }); 169 | } 170 | if(brightnessCharacteristic.listeners('get').length == 0) { 171 | brightnessCharacteristic.on("get", function(callback) { 172 | callback(null, brightnessCharacteristic.value); 173 | }); 174 | } 175 | } 176 | } 177 | 178 | module.exports = PhicommAirDetectorM1Parser; 179 | 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge-phicomm-air_detector 2 | [![npm version](https://badge.fury.io/js/homebridge-phicomm-air_detector.svg)](https://badge.fury.io/js/homebridge-phicomm-air_detector) 3 | 4 | 斐讯的空气检测仪悟空M1的HomeBridge插件。 5 | 6 | 感谢[Zack](https://github.com/promisezackr/),感谢每一位开发者和测试人员。 7 | 8 | **注: 如果你发现bug, 请提交到[issues](https://github.com/YinHangCode/homebridge-phicomm-air_detector/issues)或加[QQ群: 107927710](//shang.qq.com/wpa/qunwpa?idkey=8b9566598f40dd68412065ada24184ef72c6bddaa11525ca26c4e1536a8f2a3d).** 9 | 10 | ![](https://raw.githubusercontent.com/YinHangCode/homebridge-phicomm-air_detector/master/images/M1.jpg) 11 | 12 | ## 支持设备 13 | 1.斐讯悟空M1 14 | 15 | ## 前置工作 16 | 程序需要数据主动推送过来,可以做DNS欺骗(域名是aircat.phicomm.com)让M1设备直接发包过来,也可以tcpdump截取包再转发过来。 17 | 18 | ## 安装 19 | 1. 安装HomeBridge,具体参考[README](https://github.com/nfarina/homebridge/blob/master/README.md). 20 | 2. 确保你可以在家庭app添加设备界面中看见桥配件,否则返回第一步。 21 | 3. 执行下列命令安装该插件. 22 | ``` 23 | npm install -g homebridge-phicomm-air_detector 24 | ``` 25 | ## 配置说明 26 | listenPort是监听端口,如果让M1直接发包过来写9000。 27 | forwardAddress是转发地址,可以转发给斐讯服务器或者其它需要用数据的地方。 28 | ``` 29 | "platforms": [{ 30 | "platform": "PhicommAirDetectorPlatform", 31 | "listenPort": 9000, 32 | "forwardAddress": "220.181.112.244:9000", 33 | "deviceCfgs": [{ 34 | "temperatureDisable": false, 35 | "temperatureName": "客厅温度", 36 | "humidityDisable": false, 37 | "humidityName": "客厅湿度", 38 | "ledBulbDisable": true, 39 | "ledBulbName": "客厅空气检测仪屏幕", 40 | "airQualityDisable": false, 41 | "airQualityName": "客厅空气质量" 42 | }] 43 | }] 44 | ``` 45 | ## 相关项目 46 | 如果你想通过Domoticz使用,请参考项目: [Phicomm-M1-Domoticz-Plugin](https://github.com/promisezackr/Phicomm-M1-Domoticz-Plugin) 47 | 48 | ## 更新日志 49 | ### 0.0.2 (coming soon) 50 | 1.增加数据转发功能。 51 | 2.增加对斐讯空气检测仪悟空M1屏幕亮度的调节。 52 | ### 0.0.1 (2017-11-19) 53 | 1.支持斐讯空气检测仪悟空M1空气质量,温度,湿度功能。 54 | -------------------------------------------------------------------------------- /images/M1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YinHangCode/homebridge-phicomm-air_detector/a1936034d18d2ee6a3e07215e5b77300506ed317/images/M1.jpg -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const PhicommAirDetectorM1Parser = require('./DevicesParser/PhicommAirDetectorM1Parser'); 2 | 3 | const net = require('net'); 4 | 5 | const LogUtil = require('./lib/LogUtil'); 6 | const ConfigUtil = require('./lib/ConfigUtil'); 7 | const AccessoryUtil = require('./lib/AccessoryUtil'); 8 | 9 | var fs = require('fs'); 10 | var packageFile = require("./package.json"); 11 | var PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 12 | 13 | module.exports = function(homebridge) { 14 | if(!isConfig(homebridge.user.configPath(), "platforms", "PhicommAirDetectorPlatform")) { 15 | return; 16 | } 17 | 18 | PlatformAccessory = homebridge.platformAccessory; 19 | Accessory = homebridge.hap.Accessory; 20 | Service = homebridge.hap.Service; 21 | Characteristic = homebridge.hap.Characteristic; 22 | UUIDGen = homebridge.hap.uuid; 23 | 24 | homebridge.registerPlatform('homebridge-phicomm-air_detector', 'PhicommAirDetectorPlatform', PhicommAirDetectorPlatform, true); 25 | } 26 | 27 | function isConfig(configFile, type, name) { 28 | var config = JSON.parse(fs.readFileSync(configFile)); 29 | if("accessories" === type) { 30 | var accessories = config.accessories; 31 | for(var i in accessories) { 32 | if(accessories[i]['accessory'] === name) { 33 | return true; 34 | } 35 | } 36 | } else if("platforms" === type) { 37 | var platforms = config.platforms; 38 | for(var i in platforms) { 39 | if(platforms[i]['platform'] === name) { 40 | return true; 41 | } 42 | } 43 | } else { 44 | } 45 | 46 | return false; 47 | } 48 | 49 | function PhicommAirDetectorPlatform(log, config, api) { 50 | if(null == config) { 51 | return; 52 | } 53 | 54 | this.Accessory = Accessory; 55 | this.PlatformAccessory = PlatformAccessory; 56 | this.Service = Service; 57 | this.Characteristic = Characteristic; 58 | this.UUIDGen = UUIDGen; 59 | 60 | if(api) { 61 | this.api = api; 62 | } 63 | 64 | this.log = new LogUtil(null, log); 65 | this.ConfigUtil = new ConfigUtil(config); 66 | this.AccessoryUtil = new AccessoryUtil(); 67 | 68 | this.initServerSocket(); 69 | this.doRestThings(); 70 | 71 | this.parsers = { 72 | 'm1': new PhicommAirDetectorM1Parser(this) 73 | } 74 | 75 | this.log.info("************************************************************************"); 76 | this.log.info(" PhicommAirDetectorPlatform v"+packageFile.version+" By YinHang"); 77 | this.log.info(" GitHub: https://github.com/YinHangCode/homebridge-phicomm-air_detector "); 78 | this.log.info(" QQ Group: 107927710 "); 79 | this.log.info("************************************************************************"); 80 | this.log.info("start success..."); 81 | } 82 | 83 | PhicommAirDetectorPlatform.prototype.configureAccessory = function(accessory) { 84 | this.AccessoryUtil.add(accessory); 85 | } 86 | 87 | PhicommAirDetectorPlatform.prototype.initServerSocket = function() { 88 | var that = this; 89 | 90 | net.createServer(function(socket) { 91 | socket.on('data', that.parseMessage.bind(that)); 92 | }).listen(that.ConfigUtil.getListenPort()); 93 | that.log.info("server is listening on tcp port " + that.ConfigUtil.getListenPort() + "."); 94 | } 95 | 96 | PhicommAirDetectorPlatform.prototype.parseMessage = function(data) { 97 | var that = this; 98 | 99 | if(data.length > 34) { 100 | var sourceMac = Buffer.alloc(6); 101 | data.copy(sourceMac, 0, 17, 23); 102 | var sourceMacStr = that.getString(sourceMac).replace(/\s+/g, '').toUpperCase(); 103 | var dataStr = data.toString('utf8', 28, data.length - 6); 104 | 105 | that.log.debug(sourceMacStr); 106 | that.log.debug(dataStr); 107 | 108 | if(that.ConfigUtil.getDeviceConfig(sourceMacStr)) { 109 | that.parsers['m1'].parser(sourceMacStr, dataStr); 110 | } 111 | } 112 | } 113 | 114 | PhicommAirDetectorPlatform.prototype.getString = function(data) { 115 | if(typeof data == "string") { 116 | return data; 117 | } else if(data instanceof Buffer) { 118 | var jsonStr = JSON.stringify(data); 119 | var jsonObj = JSON.parse(jsonStr); 120 | var dataObj = jsonObj['data']; 121 | var r = ""; 122 | for(var i in dataObj) { 123 | r += (dataObj[i].toString(16).toUpperCase().length < 2 ? ('0' + dataObj[i].toString(16).toUpperCase()) : dataObj[i].toString(16).toUpperCase()) + " "; 124 | } 125 | return r; 126 | } else { 127 | } 128 | } 129 | 130 | PhicommAirDetectorPlatform.prototype.doRestThings = function(api) { 131 | var that = this; 132 | /* 133 | that.api.on('didFinishLaunching', function() { 134 | var deviceCfgs = that.config['deviceCfgs']; 135 | if(deviceCfgs instanceof Array) { 136 | for (var i = 0; i < deviceCfgs.length; i++) { 137 | var deviceCfg = deviceCfgs[i]; 138 | if(null == deviceCfg['type'] || "" == deviceCfg['type'] || null == deviceCfg['mac'] || "" == deviceCfg['mac']) { 139 | continue; 140 | } 141 | 142 | if(deviceCfg['type'] in that.parsers) { 143 | that.parsers[deviceCfg['type']].parser(deviceCfg); 144 | } 145 | } 146 | } 147 | that.api.unregisterPlatformAccessories("homebridge-simens-switch", "SimensSwitchPlatform", that.AccessoryDeleteUtil.getArrAll()); 148 | delete that.AccessoryDeleteUtil; 149 | });*/ 150 | } -------------------------------------------------------------------------------- /lib/AccessoryUtil.js: -------------------------------------------------------------------------------- 1 | class AccessoryUtil { 2 | constructor() { 3 | this.accessories = {}; 4 | } 5 | 6 | getByUUID(uuid) { 7 | return (uuid in this.accessories) ? this.accessories[uuid] : null; 8 | } 9 | 10 | add(accessory) { 11 | this.accessories[accessory.UUID] = accessory; 12 | } 13 | 14 | remove(uuid) { 15 | delete this.accessories[uuid]; 16 | } 17 | 18 | getArrAll() { 19 | var array = []; 20 | for(var item in this.accessories) { 21 | array.push(this.accessories[item]); 22 | } 23 | return array; 24 | } 25 | } 26 | 27 | module.exports = AccessoryUtil; -------------------------------------------------------------------------------- /lib/ConfigUtil.js: -------------------------------------------------------------------------------- 1 | class ConfigUtil { 2 | constructor(config) { 3 | this.config = config; 4 | 5 | } 6 | 7 | getListenPort() { 8 | return this.config['listenPort']; 9 | } 10 | 11 | getForwardAddress() { 12 | return this.config['forwardAddress']; 13 | } 14 | 15 | getDeviceConfig(deviceMacStr) { 16 | var deviceCfgs = this.config['deviceCfgs']; 17 | if(deviceCfgs instanceof Array) { 18 | for (var i = 0; i < deviceCfgs.length; i++) { 19 | var deviceCfg = deviceCfgs[i]; 20 | // if(null == deviceCfg['type'] || "" == deviceCfg['type']) { 21 | // continue; 22 | // } 23 | if(null == deviceCfg['mac'] || "" == deviceCfg['mac']) { 24 | continue; 25 | } 26 | 27 | if(deviceCfg['mac'] == deviceMacStr) { 28 | return deviceCfg; 29 | } 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | getDeviceName(deviceMacStr, name) { 36 | var deviceConfig = this.getDeviceConfig(deviceMacStr); 37 | if(deviceConfig) { 38 | return deviceConfig[name]; 39 | } 40 | return 'temp'; 41 | } 42 | } 43 | 44 | module.exports = ConfigUtil; -------------------------------------------------------------------------------- /lib/LogUtil.js: -------------------------------------------------------------------------------- 1 | class LogUtil { 2 | constructor(flag, log) { 3 | this.flag = flag; 4 | this.log = log; 5 | } 6 | 7 | debug(str) { 8 | this.log.debug(this.flag ? "[" + this.flag + "]" : "" + "[DEBUG]" + str); 9 | } 10 | 11 | info(str) { 12 | this.log.info(this.flag ? "[" + this.flag + "]" : "" + "[INFO]" + str); 13 | } 14 | 15 | warn(str) { 16 | this.log.warn(this.flag ? "[" + this.flag + "]" : "" + "[WARN]" + str); 17 | } 18 | 19 | error(str) { 20 | this.log.error(this.flag ? "[" + this.flag + "]" : "" + "[ERROR]" + str); 21 | if(str instanceof Error) { 22 | this.log.debug(this.flag ? "[" + this.flag + "]" : "" + "[ERROR]" + str.stack); 23 | } 24 | } 25 | } 26 | 27 | module.exports = LogUtil; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-phicomm-air_detector", 3 | "version": "0.0.1", 4 | "description": "phicomm air detector plugins for HomeBridge(https://github.com/nfarina/homebridge).", 5 | "keywords": [ 6 | "homebridge-plugin" 7 | ], 8 | "author": "YinHang", 9 | "license": "ISC", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/YinHangCode/homebridge-phicomm-air_detector" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/YinHangCode/homebridge-phicomm-air_detector/issues" 16 | }, 17 | "engines": { 18 | "node": ">=0.12.0", 19 | "homebridge": ">=0.4.1" 20 | } 21 | } --------------------------------------------------------------------------------