├── .gitattributes ├── .gitignore ├── LICENSE-2.0.txt ├── README.md ├── __init__.py ├── audio_server.py ├── bin ├── lehome_service ├── preprocess_cmd_history.py ├── update_autocomplete_conf.py └── webcam.sh ├── config.py ├── data └── .gitignore ├── debug_log_home.sh ├── geo_conf.json ├── geo_fencing_server.py ├── home.py ├── lib ├── __init__.py ├── command │ ├── __init__.py │ ├── parser.py │ └── runtime.py ├── helper │ ├── CameraHelper.py │ ├── GeoFencingHelper.py │ ├── MessageHelper.py │ ├── PingHelper.py │ ├── RilHelper.py │ ├── SensorHelper.py │ ├── SwitchHelper.py │ ├── TagHelper.py │ └── __init__.py ├── model │ ├── Callback.py │ ├── Elements.py │ └── __init__.py ├── sound │ ├── Sound.py │ └── __init__.py └── speech │ ├── Speech.py │ └── __init__.py ├── log_home.sh ├── mqtt_server_proxy.py ├── ping_endpoint.py ├── quick_button.py ├── remote_info_sender.py ├── remote_server_proxy.py ├── start.sh ├── stop.sh ├── tag_endpoint.py ├── usr ├── __init__.py ├── btn_conf.json ├── callbacks │ ├── __init__.py │ ├── action │ │ ├── __init__.py │ │ ├── action.py │ │ └── tools.py │ ├── compare │ │ ├── __init__.py │ │ └── compare.py │ ├── delay │ │ ├── __init__.py │ │ └── delay.py │ ├── finish │ │ ├── __init__.py │ │ └── finish.py │ ├── logical │ │ ├── __init__.py │ │ └── logical.py │ ├── next │ │ ├── __init__.py │ │ └── next.py │ ├── stop │ │ ├── __init__.py │ │ └── stop.py │ ├── target │ │ ├── __init__.py │ │ └── target.py │ ├── trigger │ │ ├── __init__.py │ │ └── trigger.py │ └── whiles │ │ ├── __init__.py │ │ └── whiles.py ├── init.json ├── res │ ├── com_begin.mp3 │ ├── com_bell.mp3 │ ├── com_bell2.mp3 │ ├── com_btn1.wav │ ├── com_btn2.wav │ ├── com_finish.mp3 │ ├── com_knock.mp3 │ ├── com_shoot.mp3 │ ├── com_start.mp3 │ ├── com_stop.mp3 │ ├── com_trash.mp3 │ └── com_warning.mp3 └── stopwords.txt ├── util ├── Res.py ├── Util.py ├── __init__.py ├── log.py └── thread.py └── vendor ├── __init__.py ├── baidu_push ├── Channel.py ├── __init__.py └── lib │ ├── ChannelException.py │ ├── RequestCore.py │ └── __init__.py ├── gpio └── __init__.py ├── ibeacon_scan ├── mipush ├── __init__.py └── mipush.py ├── xg_push ├── __init__.py ├── demo.py └── xinge.py └── yunba └── stdinpub_present /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.py[co] 3 | output.flac 4 | output_nf.wav 5 | output.wav 6 | output_o.wav 7 | usr/message/ 8 | usr/memo/ 9 | noise.* 10 | data/ 11 | # *.pcl 12 | *.swo 13 | *.log 14 | history.json 15 | -------------------------------------------------------------------------------- /LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | _/ _/_/_/_/ _/ _/ _/_/ _/ _/ _/_/_/_/ 3 | _/ _/ _/ _/ _/ _/ _/_/ _/_/ _/ 4 | _/ _/_/_/ _/_/_/_/ _/ _/ _/ _/ _/ _/_/_/ 5 | _/ _/ _/ _/ _/ _/ _/ _/ _/ 6 | _/_/_/_/ _/_/_/_/ _/ _/ _/_/ _/ _/ _/_/_/_/ 7 | 8 | ================================================================ 9 | 10 | LEHome 是一套完整的开源智能家居方案。LEHome拥有以下特性: 11 | 12 | 1. 简单的控制命令编程 13 | 2. ibeacon室内定位 14 | 3. 高度模块设计 15 | 4. 视频采集、红外控制、开关控制、传感器采集 16 | 5. android,web app,微信版客户端 17 | 18 | 项目地址:[https://github.com/legendmohe/LEHome](https://github.com/legendmohe/LEHome) 19 | 20 | UPDATE 21 | ====== 22 | 23 | 2015.5.26 添加**定位**功能 24 | 25 | example: 26 | 27 | 定位某某 28 | 29 | 服务器接受到上述命令后,即会向所有绑定的客户端以“某某”为参数发送一个定位请求。客户端收到请求后,如果“某某”与自己设定的名字是一致的,即会调用SDK发起定位,然后将结果回传给服务器。服务器再将结果发送给所有绑定的客户端。这时候客户端便可以看到“某某”的具体位置了。如下图所示: 30 | 31 | ![定位示例](http://i1334.photobucket.com/albums/w649/legendmohe/Screenshot_2015-05-26-14-39-27_zps7ycwt9rw.png) 32 | 33 | --- 34 | 35 | 2015.1.13 添加**触发器**功能 36 | 37 | example: 38 | 39 | 当打开电灯的时候播放QQ电台 40 | 41 | 当上述命令生效的时候,只要有`打开电灯`发生,`播放QQ电台`即可被触发。 42 | 43 | 注意,触发器是一次性的,如果要持续触发,请将其放在一个循环里。另外,触发的内容最 44 | 好用`#`包围,以免引起命令解析错误。例如: 45 | 46 | 循环每工作日早上7点30分内容是当#打开电灯#的时候播放qq电台 47 | 48 | 部署 49 | ==== 50 | 51 | 软件 52 | 53 | 服务端 54 | 55 | LEHome 服务端基于Python,需要安装以下依赖: 56 | 57 | [请看这里](https://gist.github.com/legendmohe/71bb6355687b5f7dfc07) 58 | 59 | down下来后,配置init.json(后面会说明如何配置),然后在根目录下运行./start.sh即可(先用chmod添加执行权限)。 60 | 61 | 客户端 62 | 63 | 目前LEHome实现了Android,web app,微信版客户端,如有需要可与我联系legendmohe@foxmail.com。 64 | 65 | 硬件 66 | 67 | 要完整地运行本项目,需要准备以下硬件: 68 | 69 | 1. reco WIFI插座 * n 70 | 2. 蓝牙4.0适配器 * 2 71 | 3. ibeacon模块 * n 72 | 4. 蓝牙音箱 * 1 73 | 5. 红外模块 * 1 74 | 6. zigbee传感器 * 2 75 | 7. UVC 摄像头 * 1 76 | 77 | #### reco WIFI插座 78 | 79 | 淘宝大概99一个,体积略大,好在控制协议是开放的,可以很方便地整合进LEHome。 80 | 81 | 买回来后,用reco的手机客户端配置一下插座使其正常工作,然后打开路由器的管理页面,将插座的ip记下来备用。 82 | 83 | 你也可以通过更改SwitchHelper.py使系统兼容自己的wifi插座。 84 | 85 | #### 蓝牙4.0适配器 86 | 87 | 由于要使用ibeacon进行室内定位,故需要4.0以上的BT适配器。需要两个是因为一个负责连蓝牙音箱,一个负责接受ibeacon数据包。如果直接使用音频线来连音箱,则只需一个适配器即可 88 | 89 | #### 蓝牙音箱 90 | 91 | 可以用普通音箱代替 :) 92 | 93 | #### 红外模块 94 | 95 | 淘宝有售,选择一个开源控制协议的即可。为了避免广告嫌疑,这里不提供链接,有需要的可以私下联系。 96 | 97 | #### zigbee传感器 98 | 99 | 淘宝有许多zigbee开发板出售,选择其中之一即可。为了避免广告嫌疑,这里不提供链接,有需要的可以私下联系。 100 | 101 | 注:要根据实际采用的红外模块和zigbee传感器模块来调整LEHome的源码(RILHelper.py和sensor_server.py)。 102 | 103 | #### UVC 摄像头 104 | 105 | 如果使用截图功能,需要一个UVC摄像头,很容易就可以买到。我使用的是罗技C270。 106 | 107 | 108 | 系统功能 109 | ======== 110 | 111 | 本系统最大的特点是能支持简单的命令编程。 112 | 113 | 你可以输入: 114 | 115 | 打开电灯 116 | 117 | 可以输入: 118 | 119 | 打开电灯然后打开风扇 120 | 121 | 可以更复杂一点: 122 | 123 | 循环每工作日晚上7点30分内容是打开风扇然后打开电灯 124 | 125 | 或者更更复杂一点: 126 | 127 | 循环每工作日晚上7点30分内容是如果我在家里那么延时10分钟打开电灯然后如果当前温度大于数值26那么打开风扇然后播放语音#你好# 128 | 129 | 130 | #### 如何查看系统支持的命令 131 | 132 | 打开usr/init.json,可以看到在"command"项下,有许多预定义的命令。 133 | 134 | 系统检测到命令词出现的时候,会调用相应的callback,所有业务逻辑都在callback里面完成。 135 | 136 | #### 命令格式 137 | 138 | 命令由基本命令和控制语句组成。准许以下规则: 139 | 140 | 1. 基本命令 = delay + action + target + message 141 | 2. 基本命令 = 基本命令 + 控制语句 142 | 3. 命令 = trigger + 基本命令 + finish/stop 143 | 144 | 例如: 145 | 146 | 打开风扇 -- 打开[action]风扇[target] 147 | 延时10分钟打开电灯 -- 延时10分钟[delay]打开[action]电灯[target] 148 | 查询公交车8路 -- 查询[action]公交车[target]8路[message] 149 | 如果我在家里那么打开电灯 -- 如果[控制语句if]我在家里[基本命令]那么[控制语句then]打开电灯[基本命令] 150 | 151 | 以上命令不能直接被系统识别,需要用trigger和finish/stop包围 152 | 153 | 例如: 154 | 155 | 你好打开风扇谢谢 -- 你好[trigger]打开[action]风扇[target]谢谢[finish] 156 | 157 | *添加trigger和finish的原因是系统支持连续语音识别命令,需要考虑断句的情况,所以要添加两个标志位来截取命令。 158 | 159 | #### 命令callback 160 | 161 | 所有命令对应的callback.py都保存在usr/callbacks/目录下。 162 | 163 | 在init.json文件中,可以通过: 164 | 165 | "callback":{ 166 | "whiles":{ 167 | "循环":"whiles.while_callback", 168 | "重复":"whiles.while_callback" 169 | }, 170 | ... 171 | } 172 | 173 | 这样来指定。 174 | 175 | callback主要如下所示: 176 | 177 | from lib.model import Callback 178 | 179 | class timer_callback(Callback.Callback): 180 | def callback(self, cmd, action, target, msg): 181 | ... 182 | 183 | 当命令词被触发时,相应callback的callback()方法会被调用,传入的参数由callback函数的定义决定。 184 | 185 | #### 应用架构 186 | 187 | 如下图所示: 188 | 189 | ![应用架构图](http://i1334.photobucket.com/albums/w649/legendmohe/LEHome_zpsg57l1hlc.png) 190 | 191 | 联系方式 192 | ======== 193 | 194 | 本项目断断续续做了一年,代码风格,逻辑实现等比较幼稚,加上本README写得极简,基本不可作为开发参考使用,故如有任何疑问,可联系legendmohe@foxmail.com。 195 | 196 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import lib 2 | import usr 3 | import util 4 | import vendor 5 | import log 6 | -------------------------------------------------------------------------------- /audio_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | # alsa settings refer to: 20 | # https://gist.github.com/legendmohe/83ba17c1e9b9c46480d2 21 | 22 | import threading 23 | import subprocess 24 | import os 25 | import signal 26 | from Queue import Queue 27 | from time import sleep 28 | import argparse 29 | 30 | import tornado.ioloop 31 | import tornado.web 32 | import alsaaudio 33 | 34 | from util.log import * 35 | 36 | SOUNDCARD_NAME = u'ALSA' 37 | 38 | class RETURNCODE: 39 | SUCCESS = '1' 40 | ERROR = '2' 41 | FAIL = '3' 42 | EMPTY = '4' 43 | NO_RES = '5' 44 | 45 | 46 | class StopHandler(tornado.web.RequestHandler): 47 | def get(self): 48 | url = self.get_argument("url", None) 49 | if url is None or url == "": 50 | INFO("url is empty") 51 | self.write(str(RETURNCODE.EMPTY)) 52 | return 53 | if stop_audio(url): 54 | self.write(RETURNCODE.FAIL) 55 | else: 56 | self.write(RETURNCODE.SUCCESS) 57 | 58 | 59 | class ClearQeueuHandler(tornado.web.RequestHandler): 60 | def get(self): 61 | clear_audio_queue() 62 | self.write(RETURNCODE.SUCCESS) 63 | 64 | 65 | class VolumeHandler(tornado.web.RequestHandler): 66 | 67 | def initialize(self): 68 | cards = alsaaudio.cards() 69 | INFO(cards) 70 | card_idx = cards.index(SOUNDCARD_NAME) 71 | self._m = alsaaudio.Mixer(control='PCM', cardindex=card_idx) 72 | INFO("use card %d." % card_idx) 73 | 74 | def get(self): 75 | DEBUG(u"正在获取音量值") 76 | try: 77 | volumes = self._m.getvolume() 78 | INFO("get volumes:%s" % volumes) 79 | volume = str(int(volumes[0])) 80 | DEBUG(u"当前音量值为:%s" % volume) 81 | self.write(volume) 82 | except Exception, e: 83 | ERROR(e) 84 | self.write("") 85 | 86 | def post(self): 87 | v_str = self.get_argument("v", default=None, strip=False) 88 | if v_str is None: 89 | self.write("-1") 90 | WARN(u"请输入音量值") 91 | return 92 | try: 93 | DEBUG("set volume:%s" % v_str) 94 | volume = int(v_str) 95 | except ValueError: 96 | try: 97 | volume = float(v_str) 98 | self.write("-2") 99 | ERROR(u"音量值必须为整数") 100 | return 101 | except ValueError: 102 | volume = -1 103 | if volume == -1: 104 | self.write("-3") 105 | ERROR(u"音量值无效:%s" % msg) 106 | return 107 | self._m.setvolume(volume) 108 | INFO(u"设置音量值为:%s" % str(volume)) 109 | self.write(RETURNCODE.SUCCESS) 110 | 111 | 112 | class PlayHandler(tornado.web.RequestHandler): 113 | def get(self): 114 | url = self.get_argument("url", None) 115 | if url is None or url == "": 116 | INFO("url is empty") 117 | self.write(str(RETURNCODE.EMPTY)) 118 | return 119 | 120 | is_inqueue = self.get_argument("inqueue", None) 121 | channel = self.get_argument("channel", "default") 122 | loop = self.get_argument("loop", None) 123 | if is_inqueue is None: 124 | if loop is None: 125 | play_audio(url, channel) 126 | else: 127 | play_audio(url, channel, int(loop)) 128 | else: 129 | if loop is None: 130 | play_audio_inqueue(url, channel) 131 | else: 132 | play_audio_inqueue(url, channel, int(loop)) 133 | self.write(str(RETURNCODE.SUCCESS)) 134 | 135 | 136 | # ============== functions ============ 137 | 138 | 139 | def worker(play_url, channel, loop): 140 | global mp_context, mixer_normal, mixer_notice 141 | 142 | cmd = [ 143 | 'sudo', 144 | 'mplayer', 145 | '-ao', 'alsa:device=%s' % channel, 146 | play_url, 147 | '-loop', str(loop)] 148 | INFO("play cmd:%s" % cmd) 149 | nor_vol = mixer_normal.getvolume()[0] 150 | if channel == 'notice': 151 | mixer_normal.setvolume(int(nor_vol*0.8)) 152 | with open(os.devnull, 'w') as tempf: 153 | player = subprocess.Popen(cmd, stdout=tempf, stderr=tempf) 154 | mp_context[play_url] = player 155 | print "player create: " + str(player.pid) 156 | player.communicate() 157 | if play_url in mp_context: 158 | del mp_context[play_url] 159 | print "play finished:%s" % (play_url,) 160 | mixer_normal.setvolume(nor_vol) 161 | 162 | 163 | def play_audio(url, channel='default', loop=1): 164 | global mp_context 165 | 166 | if url in mp_context: 167 | mp = mp_context[url] 168 | if not mp is None: # for thread-safe 169 | mp.terminate() 170 | t = threading.Thread(target=worker, args=(url, channel, loop)) 171 | t.setDaemon(True) 172 | t.start() 173 | 174 | return True 175 | 176 | 177 | def play_audio_inqueue(url, channel='default', loop=1): 178 | global mp_queue 179 | mp_queue.put((url, channel, loop)) 180 | INFO("%s was added to queue." % (url,)) 181 | 182 | 183 | def stop_audio(url): 184 | global mp_context 185 | if not url in mp_context: 186 | WARN("%s is not playing" % (url, )) 187 | return False 188 | else: 189 | mp = mp_context[url] 190 | if not mp is None: # for thread-safe 191 | mp.terminate() 192 | if url in mp_context: 193 | del mp_context[url] 194 | return True 195 | 196 | 197 | def clear_audio_queue(): 198 | global mp_context 199 | global mp_queue 200 | 201 | with mp_queue.mutex: 202 | mp_queue.queue.clear() 203 | if "queue" in mp_context: 204 | mp_context["queue"].terminate() 205 | del mp_context["queue"] 206 | 207 | 208 | def queue_worker(): 209 | global mp_context, mixer_normal, mixer_notice 210 | global mp_queue 211 | 212 | while True: 213 | url, channel, loop = mp_queue.get() 214 | print "get from queue:%s \n channel:%s" % (str(url), channel) 215 | cmd = [ 216 | 'sudo', 217 | 'mplayer', 218 | '-ao', 'alsa:device=%s' % channel, 219 | url, 220 | '-loop', str(loop)] 221 | # print cmd 222 | INFO("queue play cmd:%s" % cmd) 223 | nor_vol = mixer_normal.getvolume()[0] 224 | if channel == 'notice': 225 | # INFO("set nor_vol to 20") 226 | mixer_normal.setvolume(int(nor_vol*0.8)) 227 | with open(os.devnull, 'w') as tempf: 228 | player = subprocess.Popen(cmd, stdout=tempf, stderr=tempf) 229 | mp_context["queue"] = player 230 | player.communicate() 231 | print url + u" stopped." 232 | if "queue" in mp_context: 233 | del mp_context["queue"] 234 | mixer_normal.setvolume(nor_vol) 235 | mp_queue.task_done() 236 | sleep(1) 237 | 238 | 239 | def init_queue_player(): 240 | t = threading.Thread(target=queue_worker) 241 | t.setDaemon(True) 242 | t.start() 243 | 244 | 245 | mp_context = {} 246 | mp_queue = Queue() 247 | mixer_normal = alsaaudio.Mixer(control='chan_norl_amp') 248 | mixer_notice = alsaaudio.Mixer(control='chan_noti_amp') 249 | # http://stackoverflow.com/questions/17101502/how-to-stop-the-tornado-web-server-with-ctrlc 250 | is_closing = False 251 | def signal_handler(signum, frame): 252 | global is_closing 253 | is_closing = True 254 | 255 | 256 | def try_exit(): 257 | global is_closing, mp_context 258 | if is_closing: 259 | # clean up here 260 | tornado.ioloop.IOLoop.instance().stop() 261 | logging.info('exit success') 262 | for url in mp_context: 263 | mp = mp_context[url] 264 | mp.terminate() 265 | 266 | 267 | application = tornado.web.Application([ 268 | (r"/play", PlayHandler), 269 | (r"/clear", ClearQeueuHandler), 270 | (r"/stop", StopHandler), 271 | (r"/volume", VolumeHandler), 272 | ]) 273 | 274 | if __name__ == "__main__": 275 | parser = argparse.ArgumentParser( 276 | description='audio_server.py -p port') 277 | parser.add_argument('-p', 278 | action="store", 279 | dest="port", 280 | default="8001", 281 | ) 282 | port = parser.parse_args().port 283 | INFO("bind to %s " % (port)) 284 | 285 | signal.signal(signal.SIGINT, signal_handler) 286 | application.listen(port) 287 | init_queue_player() 288 | tornado.ioloop.PeriodicCallback(try_exit, 100).start() 289 | tornado.ioloop.IOLoop.instance().start() 290 | -------------------------------------------------------------------------------- /bin/lehome_service: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: lehome service 4 | # Required-Start: $all 5 | # Required-Stop: 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Manage lehome service 9 | ### END INIT INFO 10 | 11 | case "$1" in 12 | start) 13 | /home/ubuntu/dev/LEHome/start.sh 14 | exit 0 15 | ;; 16 | stop) 17 | /home/ubuntu/dev/LEHome/stop.sh 18 | exit 0 19 | ;; 20 | *) 21 | echo "Usage: /etc/init.d/lehome_service {start|stop}" 22 | exit 1 23 | ;; 24 | esac 25 | -------------------------------------------------------------------------------- /bin/preprocess_cmd_history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import sys 20 | import time 21 | import json 22 | 23 | import redis 24 | 25 | HISTORY_CMD_KEY = "lehome:cmd_history_list" 26 | 27 | r = redis.Redis(host='localhost', port=6379) 28 | 29 | print "dbsize:", r.dbsize() 30 | print "num of keys:", r.keys() 31 | print "volume:", r.get("lehome:last_volume") 32 | 33 | historys = r.lrange(HISTORY_CMD_KEY, 0, -1) 34 | print "history size:", len(historys) 35 | 36 | # r.delete(HISTORY_CMD_KEY) 37 | 38 | for i in range(1, 10): 39 | print historys[-i] 40 | 41 | look_up_dict = {} 42 | for item in historys: 43 | item = item.split(":") 44 | stmp = int(item[0]) 45 | cmd = item[1] 46 | if cmd not in look_up_dict: 47 | look_up_dict[cmd] = {'count': 0} 48 | look_up_dict[cmd]['count'] = look_up_dict[cmd]['count'] + 1 49 | 50 | print "dict size:", len(look_up_dict) 51 | with open("../usr/history.json", "w") as f: 52 | f.write(json.dumps(look_up_dict)) 53 | -------------------------------------------------------------------------------- /bin/update_autocomplete_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #encoding='utf-8' 3 | 4 | import urllib 5 | import urllib2 6 | import json 7 | 8 | g_conf = { 9 | "links": 10 | { 11 | "while" : ["action", "delay"], 12 | "if" : ["action", "delay"], 13 | "then" : ["if", "while", "action", "delay"], 14 | "else" : ["action", "delay"], 15 | "delay" : ["time", "action"], 16 | "trigger" : ["while", "if", "delay", "action"], 17 | "action" : ["target", "message", "next", "logical", "compare", "then", "finish"], 18 | "target" : ["message", "next", "logical", "compare", "then", "finish"], 19 | "finish" : ["trigger"], 20 | "next" : ["while", "if", "delay", "action"], 21 | "logical" : ["action", "delay"], 22 | "compare" : ["action", "delay"], 23 | "message" : ["next", "logical", "compare", "then", "finish"], 24 | "time" : ["action"] 25 | }, 26 | "message_seq":"#", 27 | "time_seq":"#", 28 | "init_state":"trigger" 29 | } 30 | 31 | 32 | 33 | def get_conf_file(id): 34 | upload_url = "http://lehome.sinaapp.com/auto/init?id=%s" % id 35 | 36 | req = urllib2.Request(upload_url) 37 | print("get: %s" % upload_url) 38 | 39 | res_data = urllib2.urlopen(req) 40 | res = res_data.read() 41 | print(res) 42 | 43 | def post_conf_file(id, data, version): 44 | if version is None: 45 | print("version is None!") 46 | return 47 | print("data:%s" % type(data)) 48 | 49 | post_url = "http://lehome.sinaapp.com/auto/init?id=%s&v=%s" % (id, version) 50 | req = urllib2.Request(url=post_url, data=data) 51 | print(req) 52 | 53 | res_data = urllib2.urlopen(req) 54 | res = res_data.read() 55 | print(res) 56 | 57 | def init_to_conf(conf_file_path): 58 | global g_conf 59 | 60 | with open(conf_file_path, 'r') as f: 61 | init_data = json.loads(f.read()) 62 | 63 | g_conf['nodes'] = init_data['command'] 64 | return json.dumps(g_conf) 65 | 66 | def main(id, conf_file_path, version): 67 | if id is None: 68 | print("id is None!") 69 | return 70 | 71 | if not conf_file_path is None: 72 | conf_file_content = init_to_conf(conf_file_path) 73 | post_conf_file(id, conf_file_content, version) 74 | else: 75 | get_conf_file(id) 76 | 77 | 78 | if __name__ == "__main__" : 79 | from argparse import ArgumentParser 80 | parser = ArgumentParser() 81 | 82 | parser.add_argument("-f", "--file", 83 | dest="conf_file_path", 84 | default=None, 85 | help="conf file path", 86 | metavar="FILE") 87 | parser.add_argument("-i", "--id", 88 | dest="id", 89 | default=None, 90 | help="id number") 91 | parser.add_argument("-v", "--version", 92 | dest="version", 93 | default=None, 94 | help="version number") 95 | 96 | args = parser.parse_args() 97 | 98 | print("id:%s conf_file_path:%s version:%s" % ( 99 | id, 100 | args.conf_file_path, 101 | args.version 102 | )) 103 | main(args.id, args.conf_file_path, args.version) 104 | -------------------------------------------------------------------------------- /bin/webcam.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DATE=$(date +"%Y-%m-%d_%H%M") 4 | 5 | fswebcam -d /dev/video1 -r 1280x720 --no-banner /home/ubuntu/dev/LEHome/data/capture/$DATE.jpg 6 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | DEBUG_ENABLE = True 20 | 21 | TMPFS_PATH = "/run/shm/lehome/" 22 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/data/.gitignore -------------------------------------------------------------------------------- /debug_log_home.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEBUG_LOG_PATH="/run/shm/lehome" 3 | if [ -d $DEBUG_LOG_PATH ]; then 4 | tail -n 100 -f $DEBUG_LOG_PATH/log/home_debug.log | grep "$@" 5 | else 6 | tail -n 100 -f log/home_debug.log | grep "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /geo_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "devices": [ 3 | "新宇" 4 | ], 5 | "area": { 6 | "家里":{ 7 | "lat": 23.0781, 8 | "lon": 113.2989 9 | } 10 | }, 11 | "min_interval": 10, 12 | "max_interval": 300, 13 | "sensitivity": 0.1, 14 | "trigger": "你好", 15 | "finish": "谢谢", 16 | "home_address": "http://localhost:8000/home/cmd", 17 | "listen_port": 8009 18 | } 19 | -------------------------------------------------------------------------------- /geo_fencing_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import threading 6 | import Queue 7 | import collections 8 | import time 9 | import urllib, urllib2 10 | import math 11 | import threading 12 | import random 13 | import os 14 | import signal 15 | import argparse 16 | 17 | import tornado.ioloop 18 | import tornado.web 19 | import redis 20 | 21 | from util.log import * 22 | 23 | # silent mode 24 | INFO = DEBUG 25 | 26 | # ----------------------Util 27 | 28 | def enum(**enums): 29 | return type('Enum', (), enums) 30 | 31 | 32 | # ---------------------- Data 33 | 34 | Event = enum(ENTER=0, LEAVE=1) 35 | 36 | 37 | class GeoResolver: 38 | def __init__(self, devices, areas, min_interval, max_interval, sensitivity): 39 | self._dev = {} 40 | self._area_state = {} 41 | self._areas = areas 42 | self._min_interval = min_interval 43 | self._max_interval = max_interval 44 | self._sensitivity = sensitivity 45 | for name, device in devices.items(): 46 | self._dev[name] = device 47 | for area_name, area in self._areas.items(): 48 | self._area_state[area_name] = {name: Event.LEAVE} 49 | 50 | def resolve(self, target_name): 51 | # self.dump() 52 | print "resolve!" 53 | dev = self._dev.get(target_name, None) 54 | if dev is not None and len(dev.loc_queue) > 1: 55 | cur_loc = dev.loc_queue[-1] 56 | last_loc = dev.loc_queue[-2] 57 | movement = GeoResolver.cal_distance(cur_loc.lat, cur_loc.lon, last_loc.lat, last_loc.lon) 58 | dev.movements.append(movement) 59 | # print "movement from", cur_loc.dump(), "to", last_loc.dump(), "is", movement 60 | 61 | intervals = [] 62 | for area_name, area in self._areas.items(): 63 | distance = GeoResolver.cal_distance(cur_loc.lat, cur_loc.lon, area.lat, area.lon) 64 | INFO("distance from " + str(cur_loc.dump()) + " to " + str(area.dump()) + " is " + str(distance)) 65 | 66 | interval = self.cal_interval(dev, distance, self._sensitivity) 67 | intervals.append(interval) 68 | 69 | if distance <= self._sensitivity and self._area_state[area_name][dev.name] == Event.LEAVE: 70 | self.notify_state(area, dev, Event.ENTER) 71 | self._area_state[area_name][dev.name] = Event.ENTER 72 | if distance >= self._sensitivity and self._area_state[area_name][dev.name] == Event.ENTER: 73 | self.notify_state(area, dev, Event.LEAVE) 74 | self._area_state[area_name][dev.name] = Event.LEAVE 75 | 76 | dev.loc_interval = min(intervals) 77 | INFO("%s sleep for %f sec, state:%d" % (dev.name, dev.loc_interval, self._area_state[area_name][dev.name])) 78 | print "%s sleep for %f sec" % (dev.name, dev.loc_interval) 79 | 80 | @staticmethod 81 | def cal_distance(lat1, lon1, lat2, lon2): 82 | # approximate radius of earth in km 83 | radius_of_earth = 6373.0 84 | 85 | lat1 = math.radians(lat1) 86 | lon1 = math.radians(lon1) 87 | lat2 = math.radians(lat2) 88 | lon2 = math.radians(lon2) 89 | 90 | dlon = lon2 - lon1 91 | dlat = lat2 - lat1 92 | 93 | a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 94 | c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) 95 | 96 | distance = radius_of_earth * c 97 | return distance 98 | 99 | def cal_interval(self, dev, distance, sensitivity): 100 | # calculation 101 | interval = 0 102 | 103 | velocity = 0 104 | clen = len(dev.loc_queue) 105 | for i in range(1, len(dev.loc_queue)): 106 | cur_loc = dev.loc_queue[i] 107 | last_loc = dev.loc_queue[i - 1] 108 | if (cur_loc.timestamp - last_loc.timestamp != 0): 109 | velocity += dev.movements[i]*1000/(cur_loc.timestamp - last_loc.timestamp) # m/s 110 | else: 111 | clen -= 1 112 | print "velocity from", cur_loc.dump(), "to", last_loc.dump(), "velocity", velocity, "interval", (-295*velocity + 210)/0.7 113 | if clen != 1: 114 | velocity /= clen - 1 115 | 116 | velocity_interval = 0 117 | if velocity <= 0.7 and not (self._sensitivity <= distance <= 2*self._sensitivity): 118 | velocity_interval = (-295*velocity + 210)/0.7 119 | velocity_interval = velocity_interval if velocity_interval > 0 else 0 120 | interval += velocity_interval 121 | 122 | distance_interval = 29.5*distance + 5 123 | interval += distance_interval 124 | 125 | print "interval:", interval, "velocity", velocity, "velocity_interval:", velocity_interval, "distance_interval", distance_interval 126 | 127 | interval = interval if interval > self._min_interval else self._min_interval 128 | interval = interval if interval < self._max_interval else self._max_interval 129 | return interval 130 | 131 | def notify_state(self, area, device, event_type): 132 | print area.name, "notify state", event_type, "for device", device.name 133 | event = "" 134 | if event_type == Event.ENTER: 135 | event = "回到" 136 | elif event_type == Event.LEAVE: 137 | event = "离开" 138 | 139 | cmd = "触发#%s%s%s#" % (device.name, event, area.name) 140 | send_cmd_to_home(cmd) 141 | 142 | def dump(self): 143 | for name, item in self._dev.items(): 144 | print name, [(loc.lat, loc.lon) for loc in list(item.loc_queue)] 145 | 146 | 147 | class Device: 148 | def __init__(self, name, default_interval=15): 149 | self.loc_queue = collections.deque(maxlen=5) 150 | self.movements = collections.deque([0], maxlen=self.loc_queue.maxlen) 151 | self.name = name 152 | self.loc_interval = default_interval 153 | 154 | def __str__(self): 155 | return "device:%s" % (self.name, ) 156 | 157 | 158 | class Location: 159 | def __init__(self, device, lat, lon, ts): 160 | self.lat = lat 161 | self.lon = lon 162 | self.device = device 163 | self.timestamp = ts 164 | 165 | def dump(self): 166 | return self.lat, self.lon 167 | 168 | def __str__(self): 169 | return "%s(%s, %s)" % (self.device, self.lat, self.lon) 170 | 171 | 172 | class Area: 173 | def __init__(self, name, lat, lon): 174 | self.lat = lat 175 | self.lon = lon 176 | self.name = name 177 | 178 | def dump(self): 179 | return self.name, self.lat, self.lon 180 | 181 | 182 | # ---------------------- Global 183 | 184 | g_loc_queue = Queue.Queue(maxsize=25) 185 | g_data_queues = {} 186 | g_devices = {} 187 | g_area = {} 188 | g_wait_locks = {} 189 | 190 | g_max_waiting_report = 1*60 191 | g_min_interval = 10 192 | g_max_interval = 15*60 193 | g_sensitivity = 0.05 194 | 195 | g_trigger_cmd = "trigger" 196 | g_finish_cmd = "finish" 197 | g_home_address = "" 198 | g_listen_port = 8009 199 | # ----------------------- Logic 200 | 201 | class LocationReportHandler(tornado.web.RequestHandler): 202 | 203 | def initialize(self, data_queues): 204 | self._data_queues = data_queues 205 | 206 | def post(self): 207 | body = self.request.body 208 | if body is None or body == "": 209 | INFO("body is empty") 210 | self.write("body param is needed") 211 | return 212 | try: 213 | INFO("receive loc report:%s", body) 214 | 215 | datas = body.split("|") 216 | name = datas[0] 217 | location_name = datas[1] 218 | lat = float(datas[2]) 219 | lon = float(datas[3]) 220 | ts = int(datas[4]) 221 | 222 | if name.startswith("*"): 223 | name = name[1:] 224 | 225 | data_queue = self._data_queues[name] 226 | new_loc = Location(name, lat, lon, ts) 227 | data_queue.put(new_loc) 228 | data_queue.task_done() 229 | INFO("put location:%s for %s" % (new_loc, name)) 230 | self.write("ok") 231 | return 232 | except Exception, e: 233 | TRACE_EX() 234 | ERROR(e) 235 | INFO("Invalid body:%s" % body) 236 | self.write("Invalid body.") 237 | return 238 | 239 | 240 | class LocationRequestHandler(tornado.web.RequestHandler): 241 | def get(self): 242 | name = self.get_argument("name", None) 243 | if name is None or name == "": 244 | INFO("name is empty") 245 | self.write("name param is needed") 246 | return 247 | request_for_location(name) 248 | 249 | 250 | def fetch_loc_worker(device, data_queue, process_queue, wait_lock): 251 | global g_max_waiting_report 252 | 253 | INFO("%s worker thread start." % device.name) 254 | process_lock = threading.Event() 255 | # request first 256 | send_geo_req_by_home(device) 257 | # begin geo fencing 258 | last_st = -1 259 | while True: # TODO - will it block here? 260 | try: 261 | location = data_queue.get(timeout=g_max_waiting_report) 262 | except Queue.Empty: 263 | INFO("report timeout, send another request") 264 | send_geo_req_by_home(device) 265 | continue 266 | 267 | if location.timestamp > last_st: # TODO - seq 268 | last_st = location.timestamp 269 | 270 | device.loc_queue.append(location) 271 | process_queue.put((process_lock, device.name)) 272 | process_queue.task_done() 273 | process_lock.wait() 274 | process_lock.clear() 275 | 276 | wait_lock.wait(timeout=device.loc_interval) 277 | wait_lock.clear() 278 | 279 | send_geo_req_by_home(device) 280 | 281 | INFO("%s worker thread stop." % device.name) 282 | 283 | 284 | def resolver_worker(): 285 | try: 286 | resolver = GeoResolver(g_devices, g_area, g_min_interval, g_max_interval, g_sensitivity) # min 10sec, max 15min, sen 200m 287 | while True: 288 | lock, target_name = g_loc_queue.get() 289 | resolver.resolve(target_name) 290 | lock.set() 291 | except KeyboardInterrupt: 292 | pass 293 | INFO("resolver worker thread exit.") 294 | 295 | 296 | def init(): 297 | load_data_from_conf("geo_conf.json") 298 | init_threads() 299 | 300 | 301 | def load_data_from_conf(path): 302 | global g_max_interval, g_min_interval, g_sensitivity, g_trigger_cmd, g_finish_cmd, g_home_address 303 | 304 | INFO("load conf:%s" % path) 305 | with open(path) as f: 306 | conf = json.load(f) 307 | 308 | devices = conf["devices"] 309 | for name in devices: 310 | INFO("load device:%s" % name) 311 | name = name.encode("utf-8") 312 | g_devices[name] = Device(name) 313 | 314 | area = conf["area"] 315 | for name, item in area.items(): 316 | INFO("load area:%s" % name) 317 | name = name.encode("utf-8") 318 | g_area[name] = Area(name, item["lat"], item["lon"]) 319 | 320 | g_min_interval = conf["min_interval"] 321 | g_max_interval = conf["max_interval"] 322 | g_sensitivity = conf["sensitivity"] 323 | 324 | g_trigger_cmd = conf['trigger'].encode("utf-8") 325 | g_finish_cmd = conf['finish'].encode("utf-8") 326 | g_home_address = conf['home_address'].encode("utf-8") 327 | g_listen_port = conf['listen_port'] 328 | 329 | 330 | def request_for_location(name): 331 | if name in g_wait_locks: 332 | g_wait_locks[name].set() 333 | 334 | def send_cmd_to_home(cmd): 335 | global g_trigger_cmd, g_finish_cmd, g_home_address 336 | 337 | cmd = "%s%s%s" % (g_trigger_cmd, cmd, g_finish_cmd) 338 | DEBUG("send cmd %s to home." % (cmd, )) 339 | 340 | try: 341 | data = {"cmd": cmd} 342 | enc_data = urllib.urlencode(data) 343 | response = urllib2.urlopen(g_home_address, 344 | enc_data, 345 | timeout=5).read() 346 | except urllib2.HTTPError, e: 347 | ERROR(e) 348 | return False 349 | except urllib2.URLError, e: 350 | ERROR(e) 351 | return False 352 | except Exception, e: 353 | ERROR(e) 354 | return False 355 | else: 356 | INFO("home response: " + response) 357 | return True 358 | 359 | def send_geo_req_by_home(device): 360 | cmd = "后台定位%s" % device.name 361 | send_cmd_to_home(cmd) 362 | 363 | 364 | def init_threads(): 365 | for name, device in g_devices.items(): 366 | wait_lock = threading.Event() 367 | data_queue = Queue.Queue() 368 | fetch_t = threading.Thread( 369 | target=fetch_loc_worker, 370 | args=(device, data_queue, g_loc_queue, wait_lock) 371 | ) 372 | fetch_t.daemon = True 373 | fetch_t.start() 374 | 375 | g_wait_locks[name] = wait_lock 376 | g_data_queues[name] = data_queue 377 | 378 | resolver_t = threading.Thread( 379 | target=resolver_worker, 380 | ) 381 | resolver_t.daemon = True 382 | resolver_t.start() 383 | 384 | 385 | def main(): 386 | global g_listen_port 387 | 388 | init() 389 | application = tornado.web.Application([ 390 | (r"/report", LocationReportHandler, dict(data_queues=g_data_queues)), 391 | (r"/request", LocationRequestHandler), 392 | ]) 393 | application.listen(g_listen_port) 394 | 395 | tornado.ioloop.PeriodicCallback(try_exit, 100).start() 396 | tornado.ioloop.IOLoop.instance().start() 397 | 398 | 399 | is_closing = False 400 | def signal_handler(signum, frame): 401 | global is_closing 402 | is_closing = True 403 | 404 | 405 | def try_exit(): 406 | global is_closing, mp_context 407 | if is_closing: 408 | # clean up here 409 | tornado.ioloop.IOLoop.instance().stop() 410 | 411 | if __name__ == "__main__": 412 | main() 413 | -------------------------------------------------------------------------------- /home.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import sys 20 | import importlib 21 | import traceback 22 | import signal 23 | import time 24 | 25 | import tornado.ioloop 26 | import tornado.web 27 | 28 | import redis 29 | 30 | import config 31 | from lib.command.runtime import Rumtime 32 | from lib.speech.Speech import Text2Speech 33 | from lib.helper.SwitchHelper import SwitchHelper 34 | from lib.helper.RilHelper import RilHelper 35 | from lib.helper.SensorHelper import SensorHelper 36 | from lib.helper.MessageHelper import MessageHelper 37 | from lib.helper.TagHelper import TagHelper 38 | from lib.helper.PingHelper import PingHelper 39 | from lib.helper.GeoFencingHelper import GeoFencingHelper 40 | from util.Res import Res 41 | from lib.sound import Sound 42 | from util.log import * 43 | 44 | 45 | # class TracePrints(object): 46 | # def __init__(self): 47 | # self.stdout = sys.stdout 48 | # def write(self, s): 49 | # self.stdout.write("Writing %r\n" % s) 50 | # traceback.print_stack(file=self.stdout) 51 | 52 | # sys.stdout = TracePrints() 53 | 54 | 55 | class Home: 56 | def __init__(self): 57 | 58 | INFO(u"==========服务器启动==========") 59 | INFO(u"DEBUG_ENABLE %s" % config.DEBUG_ENABLE) 60 | 61 | self._global_context = {} 62 | self._init_res = Res.init("init.json") 63 | self._init_storage() 64 | self._init_cmd_socket() 65 | self._init_audio_server() 66 | self._init_helper() 67 | self._init_speaker() 68 | self._init_command() 69 | 70 | self._resume = False 71 | self.runtime.init_tasklist() # load unfinished task 72 | 73 | self.publish_msg("init", u"==========服务器启动==========") 74 | 75 | def _init_command(self): 76 | INFO('initlizing command...') 77 | 78 | settings = self._init_res 79 | if settings: 80 | com_json = settings['command'] 81 | self.runtime = Rumtime({ 82 | "whiles":com_json["while"], 83 | "ifs":com_json["if"], 84 | "thens":com_json["then"], 85 | "elses":com_json["else"], 86 | "delay":com_json["delay"], 87 | "trigger":com_json["trigger"], 88 | "action":com_json["action"], 89 | "target":com_json["target"], 90 | "stop":com_json["stop"], 91 | "finish":com_json["finish"], 92 | "nexts":com_json["next"], 93 | "logical":com_json["logical"], 94 | "compare":com_json["compare"], 95 | }) 96 | self.runtime.setDEBUG(False) 97 | self.runtime.cmd_begin_callback = self._cmd_begin_callback 98 | self.runtime.cmd_end_callback = self._cmd_end_callback 99 | 100 | module_cache = {} 101 | cb_json = settings["callback"] 102 | for com_name in cb_json.keys(): 103 | cbs = cb_json[com_name] 104 | for cb_token in cbs.keys(): 105 | try: 106 | token = cbs[cb_token].encode("utf-8") 107 | if token == "" or token is None: 108 | WARN("token ", token, " no callbacks.") 109 | continue 110 | dpos = token.rindex('.') 111 | module_name = token[:dpos] 112 | class_name = token[dpos + 1:] 113 | cb_module_name = "usr.callbacks.%s.%s" % (com_name, module_name) 114 | cb_object = module_cache.get("%s.%s" % \ 115 | (cb_module_name, class_name) 116 | ) 117 | if cb_object is None: 118 | cb_module = importlib.import_module(cb_module_name) 119 | cb_object = getattr(cb_module, class_name)() 120 | cb_object.initialize( 121 | _global_context = self._global_context, 122 | _class_context = {}, 123 | _speaker = self._spk, 124 | _home = self, 125 | ) 126 | 127 | DEBUG("load callback: " + cb_module_name + " for command token:" + cb_token) 128 | self.runtime.register_callback( 129 | com_name, 130 | cb_token, 131 | cb_object) 132 | except Exception, e: 133 | ERROR("init commands faild.") 134 | ERROR(traceback.format_exc()) 135 | 136 | def _init_storage(self): 137 | host = self._init_res["storage"]["host"] 138 | port = self._init_res["storage"]["port"] 139 | INFO("initlizing storage:%s:%s" % (host, port)) 140 | self._storage = redis.Redis(host=host, port=port) 141 | if self._storage is None: 142 | ERROR("storage init faild!") 143 | 144 | def _init_speaker(self): 145 | INFO("initlizing speaker...") 146 | self._spk = Text2Speech() 147 | 148 | def _init_audio_server(self): 149 | Sound.AUDIO_SERVER_ADDRESS = self._init_res["connection"]["audio_server"] 150 | INFO("connect to audio server: %s " % (Sound.AUDIO_SERVER_ADDRESS)) 151 | 152 | def _init_cmd_socket(self): 153 | cmd_bind_port = self._init_res["connection"]["cmd_bind_port"] 154 | INFO("initlizing cmd socket, bing to:" + cmd_bind_port) 155 | 156 | self._cmd_bind_port = cmd_bind_port 157 | 158 | def _init_helper(self): 159 | publisher_ip = self._init_res["connection"]["publisher"] 160 | heartbeat_port = self._init_res["connection"]["heartbeat_port"] 161 | INFO("init message publisher: %s, heartbeat port: %s" % 162 | (publisher_ip, heartbeat_port)) 163 | self._msg_sender = MessageHelper(publisher_ip, heartbeat_port) 164 | 165 | switch_scan = SwitchHelper.BOARDCAST_ADDRESS 166 | INFO("init switch scan: " + switch_scan) 167 | self._switch = SwitchHelper() 168 | 169 | INFO("init ril helper") 170 | self._ril = RilHelper() 171 | 172 | INFO("init sensor helper") 173 | self._sensor = SensorHelper() 174 | 175 | tag_server_ip = self._init_res["connection"]["tag_server"] 176 | INFO("init tag server. %s" % tag_server_ip) 177 | self._tag = TagHelper(tag_server_ip, self._init_res["tag"]) 178 | 179 | ping_server_ip = self._init_res["connection"]["ping_server"] 180 | INFO("init ping server. %s" % ping_server_ip) 181 | self._ping = PingHelper(ping_server_ip, self._init_res["ping"]) 182 | 183 | geo_server_ip = self._init_res["connection"]["geo_fencing_server"] 184 | INFO("init geo-fencing server. %s" % geo_server_ip) 185 | self._geo = GeoFencingHelper(geo_server_ip) 186 | 187 | def _cmd_begin_callback(self, command): 188 | DEBUG("command begin: %s" % (command)) 189 | # self.publish_msg(command, u"执行: " + command) 190 | 191 | def _cmd_end_callback(self, command): 192 | DEBUG("command end: %s" % (command)) 193 | # self.publish_msg(command, "end: " + command) 194 | 195 | def publish_msg(self, sub_id, msg, cmd_type="normal"): 196 | self._msg_sender.publish_msg(sub_id, msg, cmd_type) 197 | 198 | def parse_cmd(self, cmd, persist=True): 199 | if not self._resume: 200 | timestamp = int(time.time()) 201 | if cmd.startswith("@"): 202 | cmd = cmd[1:] 203 | if persist is True: 204 | self._storage.rpush( 205 | "lehome:cmd_location_list", 206 | "%d:%s" % (timestamp, cmd) 207 | ) 208 | self.publish_msg(cmd, cmd, cmd_type="bc_loc") 209 | elif cmd.startswith("^"): 210 | cmd = cmd[1:] 211 | if persist is True: 212 | self._storage.rpush( 213 | "lehome:cmd_geo_location_list", 214 | "%d:%s" % (timestamp, cmd) 215 | ) 216 | self._geo.send_geo_report(cmd) 217 | else: 218 | INFO("parse_cmd: " + cmd) 219 | if persist is True: 220 | self._storage.rpush( 221 | "lehome:cmd_history_list", 222 | "%d:%s" % (timestamp, cmd) 223 | ) 224 | self.runtime.parse(cmd) 225 | 226 | def activate(self): 227 | Sound.notice(Res.get_res_path("sound/com_begin")) 228 | self._spk.start() 229 | self.runtime.resume_parsing() 230 | 231 | application = tornado.web.Application([ 232 | (r"/home/cmd", CmdHandler, dict(home=self)), 233 | ]) 234 | application.listen(self._cmd_bind_port.encode("utf-8")) 235 | tornado.ioloop.PeriodicCallback(try_exit, 1000).start() 236 | tornado.ioloop.IOLoop.instance().start() 237 | INFO("home activate!") 238 | 239 | def setResume(self, resume): 240 | self._resume = resume 241 | 242 | class CmdHandler(tornado.web.RequestHandler): 243 | def initialize(self, home): 244 | self.home = home 245 | 246 | def post(self): 247 | cmd = self.get_argument("cmd", default=None, strip=False) 248 | if cmd is None: 249 | self.write("error") 250 | return 251 | # INFO("get cmd through http post:%s", cmd) 252 | if cmd.count('#')%2 == 0: 253 | self.home.parse_cmd(cmd) 254 | self.write("ok") 255 | else: 256 | WARN("unmatch '%s'." % "#") 257 | self.write("ok") 258 | self.home.publish_msg("error", u"%s不匹配" % "#") 259 | 260 | is_closing = False 261 | def signal_handler(signum, frame): 262 | global is_closing 263 | is_closing = True 264 | 265 | 266 | def try_exit(): 267 | global is_closing 268 | if is_closing: 269 | # clean up here 270 | tornado.ioloop.IOLoop.instance().stop() 271 | logging.info('exit success') 272 | 273 | if __name__ == '__main__': 274 | signal.signal(signal.SIGINT, signal_handler) 275 | 276 | home = Home() 277 | home.activate() 278 | WARN("home got exception, now exit.") 279 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | import command, speech, sound, model, helper 2 | -------------------------------------------------------------------------------- /lib/command/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/lib/command/__init__.py -------------------------------------------------------------------------------- /lib/helper/CameraHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2010 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import subprocess 20 | import time 21 | import os.path 22 | 23 | from PIL import Image 24 | 25 | from util.log import * 26 | from util.Util import mkdir_p 27 | 28 | class CameraHelper(object): 29 | 30 | def __init__(self): 31 | pass 32 | 33 | def _get_thumbnail_file_name(save_path, file_name): 34 | temp = file_name.rsplit(".", 1) 35 | thumbnail_name = temp[0] + ".thumbnail." + temp[1] 36 | return thumbnail_name 37 | 38 | def _get_opt_file_name(save_path, file_name): 39 | temp = file_name.rsplit(".", 1) 40 | opt_filename = temp[0] + ".opt." + temp[1] 41 | return opt_filename 42 | 43 | # fswebcam -d /dev/video1 -r 1280x720 --no-banner /home/ubuntu/dev/LEHome/data/capture/$DATE.jpg 44 | def take_a_photo(self, save_path, file_name=None): 45 | if save_path is None or len(save_path) == 0: 46 | ERROR("save path is invaild") 47 | return None 48 | 49 | mkdir_p(save_path) 50 | if not save_path.endswith("/"): 51 | save_path += "/" 52 | if file_name is None or len(file_name) == 0: 53 | file_name = time.strftime("%Y_%m_%d_%H%M%S") + ".jpg" 54 | 55 | file_path = save_path + file_name 56 | INFO("taking photo...") 57 | subprocess.call([ 58 | "fswebcam", 59 | # "-d", "/dev/video0", 60 | "-r", "1280*720", 61 | # "--no-banner", 62 | file_path 63 | ]) 64 | # subprocess.call([ 65 | # "wget", 66 | # "-O", 67 | # file_path, 68 | # "http://192.168.1.100:8080/?action=snapshot" 69 | # ]) 70 | if not os.path.isfile(file_path) : 71 | INFO("snapshot faild. no such file:" + file_path) 72 | return None, None 73 | INFO("save orginal photo:" + file_name) 74 | 75 | size = 320, 240 76 | t_name = self._get_thumbnail_file_name(file_name) 77 | im = Image.open(file_path) 78 | im.thumbnail(size, Image.ANTIALIAS) 79 | im.save(save_path + t_name, "JPEG") 80 | INFO("save thumbnail photo:" + t_name) 81 | return file_name, t_name 82 | 83 | 84 | if __name__ == "__main__": 85 | import os 86 | os.chdir("/home/ubuntu/dev/LEHome/") 87 | CameraHelper().take_a_photo( 88 | save_path="/home/ubuntu/dev/LEHome/data/capture/" 89 | ) 90 | -------------------------------------------------------------------------------- /lib/helper/GeoFencingHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import time 20 | import urllib, urllib2 21 | 22 | import requests 23 | 24 | from util.log import * 25 | 26 | 27 | class GeoFencingHelper(object): 28 | 29 | def __init__(self, server_address): 30 | self.server_address = server_address 31 | 32 | def send_geo_report(self, content): 33 | if not content is None and not content == "": 34 | DEBUG("send geo report %s to geo fencing server." % (content, )) 35 | try: 36 | url = self.server_address + "/report" 37 | content = content.encode("utf-8") 38 | response = requests.post(url, data=content, timeout=5) 39 | if response.status_code != 200: 40 | return False 41 | # enc_data = content.encode("utf-8") 42 | # INFO("enc_data") 43 | # response = urllib2.urlopen(url, 44 | # enc_data, 45 | # timeout=5).read() 46 | except urllib2.HTTPError, e: 47 | ERROR(e) 48 | return False 49 | except urllib2.URLError, e: 50 | ERROR(e) 51 | return False 52 | except Exception, e: 53 | TRACE_EX() 54 | ERROR(e) 55 | return False 56 | else: 57 | DEBUG("geo server response: " + response.text) 58 | return True 59 | else: 60 | ERROR("conent is invaild.") 61 | return False 62 | -------------------------------------------------------------------------------- /lib/helper/MessageHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import threading 20 | import socket 21 | import pickle 22 | import time 23 | import json 24 | import zmq 25 | from Queue import Queue, Empty 26 | from util.Res import Res 27 | from util.log import * 28 | from util.thread import TimerThread 29 | 30 | 31 | class MessageHelper(object): 32 | 33 | LOCAL_HEARTBEAT_RATE = 5 34 | MESSAGE_DB = "./data/msg.pcl" 35 | 36 | def __init__(self, pub_address, hb_port): 37 | self.pub_address = pub_address 38 | self.heartbeat_port = int(hb_port) 39 | self._data_lock = threading.Lock() 40 | self._msg_lock = threading.Lock() 41 | self._msg_queue = Queue() 42 | 43 | self._init_data() 44 | self._init_pub_heartbeat() 45 | self._init_worker() 46 | 47 | def _init_data(self): 48 | self._load_data() 49 | if 'seq' not in self._context: 50 | self._context['seq'] = 1 51 | 52 | def _load_data(self): 53 | self._context = {} 54 | with self._data_lock: 55 | try: 56 | with open(MessageHelper.MESSAGE_DB, "rb") as f: 57 | self._context = pickle.load(f) 58 | except: 59 | INFO("empty todo list.") 60 | return self._context 61 | 62 | def _save_data(self): 63 | with self._data_lock: 64 | try: 65 | with open(MessageHelper.MESSAGE_DB, "wb") as f: 66 | pickle.dump(self._context, f, True) 67 | except Exception, e: 68 | ERROR(e) 69 | ERROR("invaild MessageHelp data path:%s", MessageHelper.MESSAGE_DB) 70 | 71 | def _msg_worker(self): 72 | context = zmq.Context() 73 | publisher = self.pub_address 74 | _pub_sock = context.socket(zmq.PUB) 75 | INFO("pub bind to : %s " % (publisher)) 76 | _pub_sock.bind(publisher) 77 | self._pub_sock = _pub_sock 78 | 79 | # for sending init string too fast 80 | time.sleep(0.5) 81 | while True: 82 | msg_string = self._get_msg() 83 | self._pub_sock.send_string(msg_string) 84 | time.sleep(0.3) 85 | 86 | def _put_msg(self, msg): 87 | self._msg_queue.put(msg) 88 | 89 | def _get_msg(self): 90 | msg = self._msg_queue.get( 91 | block=True, 92 | ) # block! 93 | self._msg_queue.task_done() 94 | return msg 95 | 96 | def _init_pub_heartbeat(self): 97 | if self.heartbeat_port is None or self.heartbeat_port == 0: 98 | WARN("heartbeat port is invalid. heartbeat is disabled") 99 | return 100 | 101 | def heartbeat(): 102 | self.publish_msg(None, "", "heartbeat") 103 | timer = TimerThread(interval=20, target=heartbeat) 104 | timer.start() 105 | 106 | local_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 107 | local_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) 108 | local_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 109 | def local_heartbeat(): 110 | address = ("255.255.255.255", self.heartbeat_port) 111 | local_sock.sendto("ok", address) 112 | 113 | local_heartbeat_thread = TimerThread( 114 | interval=MessageHelper.LOCAL_HEARTBEAT_RATE, 115 | target=local_heartbeat 116 | ) 117 | local_heartbeat_thread.start() 118 | 119 | def _init_worker(self): 120 | self._msg_thread = threading.Thread(target=self._msg_worker) 121 | self._msg_thread.daemon = True 122 | self._msg_thread.start() 123 | 124 | def publish_msg(self, sub_id, msg, cmd_type="normal"): 125 | msg_dict = { 126 | "type": cmd_type, 127 | "msg": msg, 128 | "ts": "%d" % int(time.time()), 129 | } 130 | with self._msg_lock: 131 | if cmd_type != "heartbeat": 132 | self._context['seq'] += 1 133 | msg_dict["seq"] = self._context['seq'] 134 | msg_string = json.dumps(msg_dict) 135 | DEBUG("push msg:%s-%s-%s" % (self._context['seq'], cmd_type, msg)) 136 | else: 137 | msg_dict["seq"] = -1 138 | msg_string = json.dumps(msg_dict) 139 | self._put_msg(msg_string) 140 | self._save_data() 141 | 142 | def cur_seq(self): 143 | return self._context['seq'] 144 | -------------------------------------------------------------------------------- /lib/helper/PingHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import threading 19 | import json 20 | import time 21 | import zmq 22 | from util.Res import Res 23 | from util.log import * 24 | 25 | class PingHelper(object): 26 | 27 | def __init__(self, server_ip, settings): 28 | self._server_ip = server_ip 29 | self._devices = settings["device"] 30 | 31 | def device_ip_for_name(self, name): 32 | return self._devices.get(name) 33 | 34 | def online(self, device_ip): 35 | rep = self._send_request(device_ip) 36 | if rep is None: 37 | return None 38 | res = json.loads(rep)["res"] 39 | if res == "error": 40 | INFO('ping server error.') 41 | return None 42 | return res['online'] 43 | 44 | def _send_request(self, cmd): 45 | DEBUG("send tag request to %s for %s" % (self._server_ip, cmd)) 46 | context = zmq.Context() 47 | socket = context.socket(zmq.REQ) 48 | socket.setsockopt(zmq.LINGER, 0) 49 | socket.connect(self._server_ip) 50 | socket.send_string(cmd) 51 | rep = None 52 | try: 53 | poller = zmq.Poller() 54 | poller.register(socket, zmq.POLLIN) 55 | if poller.poll(5*1000): 56 | rep = socket.recv_string() 57 | DEBUG("recv msgs:" + rep) 58 | except: 59 | WARN("socket timeout.") 60 | socket.close() 61 | return rep 62 | -------------------------------------------------------------------------------- /lib/helper/RilHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import threading 19 | import json 20 | import time 21 | import socket 22 | from util.thread import TimerThread 23 | from util.Res import Res 24 | from util.log import * 25 | 26 | 27 | class RilHelper: 28 | 29 | SOCKET_TIMEOUT = 3 30 | RETRY_TIME = 3 31 | RIL_PORT = 60000 32 | 33 | def __init__(self): 34 | init_json = Res.init("init.json") 35 | self._address = init_json["ril_address"] 36 | self._send_lock = threading.Lock() 37 | 38 | def send_cmd(self, cmd): 39 | return self._send_cmd(self._address, cmd) 40 | 41 | def _get_switch_cmd(self, action): 42 | return "%s" % action 43 | 44 | def _send_cmd(self, target_ip, cmd): 45 | if target_ip is None or len(target_ip) == 0: 46 | ERROR("invaild ril_ip.") 47 | return 48 | if cmd is None or len(cmd) == 0: 49 | ERROR("empty ril cmd.") 50 | return 51 | 52 | with self._send_lock: 53 | INFO("ril send command:%s to:%s:%s" 54 | % (cmd, target_ip, RilHelper.RIL_PORT)) 55 | for i in range(0, RilHelper.RETRY_TIME): 56 | try: 57 | # sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 58 | # server_address = (target_ip, RilHelper.RIL_PORT) 59 | # sock.connect(server_address) 60 | sock = socket.create_connection( 61 | (target_ip, RilHelper.RIL_PORT), 62 | RilHelper.SOCKET_TIMEOUT) 63 | time.sleep(0.5) 64 | sock.send(cmd) 65 | recv = sock.recv(512) 66 | sock.close() 67 | 68 | INFO("ril recv:%s" % recv) 69 | return recv.strip() 70 | except socket.timeout: 71 | ERROR("RilHelper cmd socket timeout.") 72 | except Exception, ex: 73 | ERROR(ex) 74 | return None 75 | -------------------------------------------------------------------------------- /lib/helper/SensorHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import threading 19 | import json 20 | import time 21 | import socket 22 | # import urllib 23 | # import urllib2 24 | # import zmq 25 | from util.Res import Res 26 | from util.log import * 27 | 28 | 29 | class SensorHelper: 30 | TYPE_TEMP = "T" 31 | TYPE_HUM = "H" 32 | TYPE_PIR = "P" 33 | TYPE_LUM = "L" 34 | TYPE_ALL = "A" 35 | 36 | def __init__(self): 37 | self._sock = None 38 | init_json = Res.init("init.json") 39 | try: 40 | self.place2ip = init_json["sensor"] 41 | except Exception, e: 42 | ERROR(e) 43 | ERROR("invaild SensorHelper init json.") 44 | self.place2ip = {} 45 | 46 | self._send_lock = threading.Lock() 47 | 48 | def get_places(self): 49 | if self.place2ip is None: 50 | return None 51 | return self.place2ip.keys() 52 | 53 | def addr_for_place(self, place): 54 | return self.place2ip.get(place, None) 55 | 56 | def place_for_addr(self, addr): 57 | for place in self.place2ip: 58 | if self.place2ip[place] == addr: 59 | return place 60 | return None 61 | 62 | def get_all(self, target_addr): 63 | return self.get_sensor_value(target_addr, SensorHelper.TYPE_ALL) 64 | 65 | def get_temp(self, target_addr): 66 | return self.get_sensor_value(target_addr, SensorHelper.TYPE_TEMP) 67 | 68 | def get_humidity(self, target_addr): 69 | return self.get_sensor_value(target_addr, SensorHelper.TYPE_HUM) 70 | 71 | def get_pir(self, target_addr): 72 | return self.get_sensor_value(target_addr, SensorHelper.TYPE_PIR) 73 | 74 | def get_brightness(self, target_addr): 75 | lig = self.get_sensor_value(target_addr, SensorHelper.TYPE_LUM) 76 | return lig 77 | 78 | def get_sensor_value(self, addr, cmd): 79 | try: 80 | addr, port = addr.split(":") 81 | port = int(port) 82 | except Exception, e: 83 | ERROR(e) 84 | ERROR("invaild place address format: %s" % addr) 85 | rep = self.send_cmd(addr, port, cmd) 86 | return rep 87 | 88 | def send_cmd(self, addr, port, cmd): 89 | if cmd is None or len(cmd) == 0: 90 | ERROR("invaild sensor cmd.") 91 | return 92 | DEBUG("sending cmd to place: %s:%d %s" % (addr, port, cmd)) 93 | 94 | rep = "" 95 | with self._send_lock: 96 | # import pdb 97 | # pdb.set_trace() 98 | # if self._sock is None: 99 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 100 | self._sock.settimeout(5) 101 | self._sock.connect((addr, port)) 102 | try: 103 | self._sock.send(cmd) 104 | rep = self._sock.recv(2048) 105 | DEBUG("place rep:" + rep) 106 | except Exception, ex: 107 | ERROR(ex) 108 | ERROR("can't connect to place.") 109 | self._sock.close() 110 | self._sock = None 111 | return rep 112 | 113 | def readable(self, src, cmd_type): 114 | if src is None or len(src) == 0: 115 | return "" 116 | try: 117 | if cmd_type == SensorHelper.TYPE_TEMP: 118 | return u"温度:%s℃" % src 119 | elif cmd_type == SensorHelper.TYPE_HUM: 120 | return u"湿度:%s%%" % src 121 | elif cmd_type == SensorHelper.TYPE_PIR: 122 | return u"是否有人:%s" % u'否' if src == "0" else u'是' 123 | elif cmd_type == SensorHelper.TYPE_LUM: 124 | return u"光照:%s" % src 125 | elif cmd_type == SensorHelper.TYPE_ALL: 126 | data = src.split(",") 127 | ret = u"温度:%s℃, 湿度:%s%%" \ 128 | % ( 129 | data[0], 130 | data[1], 131 | ) 132 | # ret = u"温度:%s℃, 湿度:%s%%, 是否有人:%s, 光照:%s" \ 133 | # % ( 134 | # data[0], 135 | # data[1], 136 | # (u'否' if data[2] == "0" else u'是'), 137 | # data[3] 138 | # ) 139 | return ret 140 | except Exception, ex: 141 | ERROR(ex) 142 | ERROR("invaild readable src:%s" % src) 143 | return "" 144 | -------------------------------------------------------------------------------- /lib/helper/SwitchHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import threading 19 | import json 20 | import time 21 | import socket 22 | from util.thread import TimerThread 23 | from util.Res import Res 24 | from util.log import * 25 | 26 | 27 | class SwitchHelper: 28 | 29 | HEARTBEAT_RATE = 3 30 | SOCKET_TIMEOUT = 5 31 | RETRY_TIME = 3 32 | SCAN_PORT = 48899 33 | SWITCH_PORT = 8899 34 | BOARDCAST_ADDRESS = "255.255.255.255" 35 | 36 | def __init__(self): 37 | init_json = Res.init("init.json") 38 | self.scan_ip = SwitchHelper.BOARDCAST_ADDRESS 39 | self.name2ip = init_json["switchs"] 40 | 41 | self._send_lock = threading.Lock() 42 | self.switchs = {} 43 | self._init_heartbeat() 44 | 45 | def _init_heartbeat(self): 46 | self._init_heartbeat_socket() 47 | self._send_hb_thread = threading.Thread(target=self._heartbeat_recv) 48 | self._send_hb_thread.daemon = True 49 | self._send_hb_thread.start() 50 | 51 | self._heartbeat_thread = TimerThread( 52 | interval=SwitchHelper.HEARTBEAT_RATE, 53 | target=self._heartbeat_send 54 | ) 55 | self._heartbeat_thread.start() 56 | 57 | def _init_heartbeat_socket(self): 58 | self._hb_sock = self._get_udp_socket() 59 | bind_address = ('0.0.0.0', SwitchHelper.SCAN_PORT) 60 | self._hb_sock.bind(bind_address) 61 | self._hb_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) 62 | self._hb_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) 63 | # self._hb_sock.settimeout(SwitchHelper.HEARTBEAT_RATE/2) 64 | 65 | def ip_for_name(self, name): 66 | return self.name2ip.get(name, None) 67 | 68 | def name_for_ip(self, ip): 69 | for name in self.name2ip: 70 | if self.name2ip[name] == ip: 71 | return name 72 | return None 73 | 74 | def send_open(self, target_ip): 75 | if not target_ip in self.switchs: 76 | ERROR("target_ip not exist: " + target_ip) 77 | return 78 | cmd = self._get_switch_cmd("ON") 79 | res = self._send_cmd(target_ip, cmd) 80 | INFO("send_open:%s" % res) 81 | if res == "+OK" or res == "+ok": 82 | self.switchs[target_ip]['status'] = "on" 83 | return res 84 | 85 | def send_close(self, target_ip): 86 | if not target_ip in self.switchs: 87 | ERROR("target_ip not exist: " + target_ip) 88 | return 89 | cmd = self._get_switch_cmd("OFF") 90 | res = self._send_cmd(target_ip, cmd) 91 | INFO("send_close:%s" % res) 92 | if res == "+OK" or res == "+ok": 93 | self.switchs[target_ip]['status'] = "off" 94 | return res 95 | 96 | def show_state(self, target_ip): 97 | if not target_ip in self.switchs: 98 | ERROR("target_ip not exist: " + target_ip) 99 | return None 100 | return self.switchs[target_ip]["status"] 101 | 102 | def show_info(self, target_ip): 103 | if not target_ip in self.switchs: 104 | ERROR("target_ip not exist: " + target_ip) 105 | return 106 | cmd = self._get_info_cmd() 107 | recv = self._send_cmd(target_ip, cmd) 108 | if recv is None or len(recv) == 0: 109 | return None 110 | info = recv[5:-1].split(",") 111 | return { 112 | "I": info[0] if len(info[0]) != 0 else "0", 113 | "U": info[1] if len(info[1]) != 0 else "0", 114 | "F": info[2] if len(info[2]) != 0 else "0", 115 | "P": info[3] if len(info[3]) != 0 else "0", 116 | "PQ": info[4] if len(info[4]) != 0 else "0", 117 | "E": info[5] if len(info[5]) != 0 else "0", 118 | "EQ": info[6] if len(info[6]) != 0 else "0", 119 | } 120 | 121 | def readable_info(self, info): 122 | if info is None or len(info) == 0: 123 | return "" 124 | I = "%.2f" % (float(info["I"])/100.0) + "A" 125 | U = "%.2f" % (float(info["U"])/100.0) + "V" 126 | F = "%.2f" % (float(info["F"])/100.0) + "Hz" 127 | P = "%.2f" % (float(info["P"])/10.0) + "W" 128 | PQ = info["P"] + "W" 129 | E = info["E"] + "WH" 130 | EQ = info["EQ"] + "WH" 131 | return "".join([ 132 | u"功率:%s " % P, 133 | u"电流:%s " % I, 134 | u"电压:%s " % U, 135 | # u"频率:%s " % F, 136 | # u"有功功率:%s " % P, 137 | # u"无功功率:%s " % PQ, 138 | # u"有功能量值:%s " % E, 139 | # u"无功能量值:%s" % EQ, 140 | ]) 141 | 142 | def _format_time(self): 143 | return time.strftime("%Y%m%d%H%M%S", time.localtime()) 144 | 145 | def _get_udp_socket(self): 146 | return socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 147 | 148 | def _get_cmd_socket(self): 149 | return socket.socket(socket.AF_INET, socket.SOCK_STREAM) 150 | 151 | def _get_switch_cmd(self, action): 152 | return "AT+YZSWITCH=1,%s,%s\r\n" % (action, self._format_time()) 153 | 154 | def _get_info_cmd(self): 155 | return "AT+YZOUT\r\n" 156 | 157 | def _get_heartbeat_cmd(self): 158 | return 'YZ-RECOSCAN' 159 | 160 | def _send_cmd(self, target_ip, cmd): 161 | if target_ip is None or len(target_ip) == 0: 162 | ERROR("invaild target_ip.") 163 | return 164 | if cmd is None or len(cmd) == 0: 165 | ERROR("invaild switch cmd.") 166 | return 167 | 168 | with self._send_lock: 169 | # sock = self._get_cmd_socket() 170 | # sock.connect() 171 | INFO("Switch send command:%s to:%s" % (cmd, target_ip)) 172 | for i in range(0, SwitchHelper.RETRY_TIME): 173 | try: 174 | sock = socket.create_connection( 175 | (target_ip, SwitchHelper.SWITCH_PORT), 176 | SwitchHelper.SOCKET_TIMEOUT) 177 | time.sleep(0.5) 178 | sock.send(cmd) 179 | recv = sock.recv(512) 180 | sock.close() 181 | return recv.strip() 182 | except socket.timeout: 183 | ERROR("SwitchHelper cmd socket timeout.") 184 | except Exception, ex: 185 | ERROR(ex) 186 | return None 187 | 188 | def _heartbeat_send(self): 189 | # for ip in self.switchs: 190 | # self.switchs[ip]["status"] = "-1" 191 | 192 | sock = self._hb_sock 193 | address = (self.scan_ip, SwitchHelper.SCAN_PORT) 194 | sock.sendto(self._get_heartbeat_cmd(), address) 195 | DEBUG("send switch heartbeat to:%s" % (address, )) 196 | 197 | def _heartbeat_recv(self): 198 | sock = self._hb_sock 199 | while True: 200 | try: 201 | recv, address = sock.recvfrom(512) 202 | DEBUG("recv switch heartbeat:%s from:%s" % (recv, address)) 203 | status = recv.strip().split(',') 204 | if len(status) < 5: 205 | continue 206 | 207 | switch = {} 208 | switch["ip"] = status[0] 209 | switch["mac"] = status[1] 210 | switch["name"] = self.name_for_ip(status[0]) 211 | switch["status"] = "on" if status[4] == "1" else "off" 212 | self.switchs[switch["ip"]] = switch 213 | 214 | except socket.timeout: 215 | WARN("heartbeat timeout. ") 216 | 217 | -------------------------------------------------------------------------------- /lib/helper/TagHelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2014 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | import threading 19 | import json 20 | import time 21 | import zmq 22 | from util.Res import Res 23 | from util.log import * 24 | 25 | class TagHelper(object): 26 | 27 | def __init__(self, server_ip, settings): 28 | self._server_ip = server_ip 29 | self._place = settings["place"] 30 | self._member = settings["member"] 31 | 32 | def member_id_for_name(self, name): 33 | return self._member.get(name) 34 | 35 | def place_id_for_name(self, name): 36 | return self._place.get(name) 37 | 38 | def near(self, member_id, place_id): 39 | cmd = "%s,%s" % (place_id, member_id) 40 | rep = self._send_request(cmd) 41 | if rep is None: 42 | return None 43 | res = json.loads(rep)["res"] 44 | if res == "error": 45 | INFO('tag server error.') 46 | return None 47 | status = res['status'] 48 | if status == 'unknown': 49 | return False 50 | distance = res['distance'] 51 | return False if distance > 6.0 else True 52 | 53 | def _send_request(self, cmd): 54 | DEBUG("send tag request to %s for %s" % (self._server_ip, cmd)) 55 | context = zmq.Context() 56 | socket = context.socket(zmq.REQ) 57 | socket.setsockopt(zmq.LINGER, 0) 58 | socket.connect(self._server_ip) 59 | socket.send_string(cmd) 60 | rep = None 61 | try: 62 | poller = zmq.Poller() 63 | poller.register(socket, zmq.POLLIN) 64 | if poller.poll(5*1000): 65 | rep = socket.recv_string() 66 | DEBUG("recv msgs:" + rep) 67 | except: 68 | WARN("socket timeout.") 69 | socket.close() 70 | return rep 71 | -------------------------------------------------------------------------------- /lib/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/lib/helper/__init__.py -------------------------------------------------------------------------------- /lib/model/Callback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import inspect 20 | from util.log import * 21 | 22 | 23 | class Callback(object): 24 | def __init__(self): 25 | if not callable(getattr(self, "callback", None)): 26 | ERROR("callback method not found.") 27 | return 28 | self.callback_param_names = inspect.getargspec(self.callback)[0] 29 | DEBUG(self.callback_param_names) 30 | if "self" in self.callback_param_names: 31 | self.callback_param_names.remove("self") 32 | 33 | if callable(getattr(self, "canceled", None)): 34 | self.canceled_param_names = inspect.getargspec( 35 | self.canceled 36 | )[0] 37 | DEBUG(self.canceled_param_names) 38 | if "self" in self.canceled_param_names: 39 | self.canceled_param_names.remove("self") 40 | 41 | 42 | def initialize(self, **kwargs): 43 | for k in kwargs: 44 | setattr(self, k, kwargs[k]) 45 | if callable(getattr(self, "init", None)): 46 | self.init() 47 | 48 | def internal_callback(self, **kwargs): 49 | call_dict = {} 50 | for key in self.callback_param_names: 51 | if key in kwargs: 52 | call_dict[key] = kwargs[key] 53 | else: 54 | call_dict[key] = None 55 | # DEBUG("callback: %s" % (kwargs, )) 56 | return self.callback(**call_dict) 57 | 58 | def internal_canceled(self, **kwargs): 59 | if not callable(getattr(self, "canceled", None)): 60 | return 61 | call_dict = {} 62 | for key in self.canceled_param_names: 63 | if key in kwargs: 64 | call_dict[key] = kwargs[key] 65 | else: 66 | call_dict[key] = None 67 | # DEBUG("canceled: %s" % (kwargs, )) 68 | return self.canceled(**call_dict) 69 | -------------------------------------------------------------------------------- /lib/model/Elements.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import sys 18 | 19 | class Statement: 20 | def __init__(self): 21 | self.delay = "" 22 | self.delay_time = "" 23 | self.trigger = "" 24 | self.nexts = "" 25 | self.action = "" 26 | self.target = "" 27 | self.msg = "" 28 | self.stop = "" 29 | self.finish = "" 30 | self.ifs = "" 31 | self.thens = "" 32 | self.elses = "" 33 | self.whiles = "" 34 | 35 | def __str__(self): 36 | res = u"" 37 | for attr in vars(self): 38 | ele = getattr(self, attr) 39 | res += u"self.%s = %s\n" % (attr, ele) 40 | # sys.stdout.flush() # what? 41 | return res.encode('utf-8') 42 | 43 | 44 | class Block: 45 | def __init__(self): 46 | self.statements = [] 47 | 48 | def __str__(self): 49 | res = "block:\n" 50 | for statement in self.statements: 51 | res += str(statement) + '\n ' 52 | res += '---block end.\n' 53 | return res 54 | 55 | 56 | class LogicalOperator: 57 | def __init__(self): 58 | self.name = "" 59 | self.block = Block() 60 | 61 | def __str__(self): 62 | res = 'LogicalOperator: ' + self.name.encode('utf-8') + '\n' 63 | res += str(self.block) 64 | return res 65 | 66 | 67 | class CompareOperator: 68 | def __init__(self): 69 | self.name = "" 70 | self.statement = Statement() 71 | 72 | def __str__(self): 73 | res = 'CompareOperator: ' + self.name.encode('utf-8') + '\n' 74 | res += str(self.statement) 75 | return res 76 | 77 | 78 | class IfStatement: 79 | def __init__(self): 80 | self.if_block = Block() 81 | self.then_block = Block() 82 | self.else_block = Block() 83 | 84 | def __str__(self): 85 | res = 'IfStatement: \n' 86 | res += str(self.if_block) + '\n' 87 | res += '---then:\n' 88 | res += str(self.then_block) + '\n' 89 | res += '---else:\n' 90 | res += str(self.else_block) + '\n' 91 | res += '---end if\n' 92 | return res 93 | 94 | 95 | class WhileStatement: 96 | def __init__(self): 97 | self.if_block = Block() 98 | self.then_block = Block() 99 | 100 | def __str__(self): 101 | res = 'WhileStatement: \n' 102 | res += str(self.if_block) + '\n' 103 | res += str(self.then_block) + '\n' 104 | return res 105 | 106 | # import json 107 | # 108 | # class Encoder(json.JSONEncoder): 109 | # def __init__(self): 110 | # json.JSONEncoder.__init__(self, indent=4) 111 | # 112 | # def default(self, obj): 113 | # #convert object to a dict 114 | # d = {} 115 | # d['__class__'] = obj.__class__.__name__ 116 | # d['__module__'] = obj.__module__ 117 | # d.update(obj.__dict__) 118 | # return d 119 | # 120 | # class Decoder(json.JSONDecoder): 121 | # def __init__(self): 122 | # json.JSONDecoder.__init__(self,object_hook=self.dict2object) 123 | # 124 | # def dict2object(self, d): 125 | # #convert dict to object 126 | # if'__class__' in d: 127 | # class_name = d.pop('__class__') 128 | # module_name = d.pop('__module__') 129 | # module = __import__(module_name) 130 | # class_ = getattr(module,class_name) 131 | # args = dict( 132 | # (key.encode('ascii'), value) for key, value in d.items() 133 | # ) #get args 134 | # inst = class_(**args) #create new instance 135 | # else: 136 | # inst = d 137 | # return inst 138 | -------------------------------------------------------------------------------- /lib/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/lib/model/__init__.py -------------------------------------------------------------------------------- /lib/sound/Sound.py: -------------------------------------------------------------------------------- 1 | #!usr/bin/env python 2 | #coding=utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import urllib 20 | import urllib2 21 | from util.log import * 22 | 23 | 24 | AUDIO_SERVER_ADDRESS = None 25 | 26 | def get_play_request_url(path, inqueue, channel, loop=-1): 27 | global AUDIO_SERVER_ADDRESS 28 | if AUDIO_SERVER_ADDRESS is None: 29 | WARN("audio server address is empty.") 30 | return None 31 | values = {'url': path} 32 | if inqueue is not None: 33 | values["inqueue"] = True 34 | if channel is not None: 35 | values["channel"] = channel 36 | if not loop == -1: 37 | values["loop"] = loop 38 | data = urllib.urlencode(values) 39 | return AUDIO_SERVER_ADDRESS + '/play?' + data 40 | 41 | 42 | def get_clear_request_url(): 43 | global AUDIO_SERVER_ADDRESS 44 | if AUDIO_SERVER_ADDRESS is None: 45 | WARN("audio server address is empty.") 46 | return None 47 | return AUDIO_SERVER_ADDRESS + '/clear' 48 | 49 | 50 | def get_volume_url(): 51 | global AUDIO_SERVER_ADDRESS 52 | if AUDIO_SERVER_ADDRESS is None: 53 | WARN("audio server address is empty.") 54 | return None 55 | return AUDIO_SERVER_ADDRESS + '/volume' 56 | 57 | 58 | def play(path, inqueue=False, channel='default', loop=-1): 59 | url = get_play_request_url(path, inqueue, channel, loop) 60 | if url is None: 61 | return 62 | INFO("sending audio url: " + url) 63 | try: 64 | response = urllib2.urlopen(url).read() 65 | except urllib2.HTTPError, e: 66 | INFO(e) 67 | WARN("audio server address is invaild") 68 | except urllib2.URLError, e: 69 | INFO(e) 70 | WARN("audio server unavailable.") 71 | else: 72 | INFO("audio response: " + response) 73 | 74 | def notice(path, inqueue=False, loop=-1): 75 | play(path, inqueue, channel='notice', loop=loop) 76 | 77 | def stop(path): 78 | pass 79 | 80 | def set_volume(value): 81 | url = get_volume_url() 82 | if url is None: 83 | return 84 | response = None 85 | try: 86 | value = int(value) 87 | if value < 0: 88 | value = 0 89 | elif value > 100: 90 | value = 100 91 | INFO("setting audio volume: %s" % value) 92 | data = {"v":value} 93 | enc_data = urllib.urlencode(data) 94 | response = urllib2.urlopen(url, enc_data).read() 95 | except urllib2.HTTPError, e: 96 | INFO(e) 97 | WARN("audio server address is invaild") 98 | except urllib2.URLError, e: 99 | INFO(e) 100 | WARN("audio server unavailable.") 101 | except Exception, e: 102 | ERROR(e) 103 | else: 104 | INFO("audio set volume response: " + response) 105 | return response 106 | 107 | def get_volume(): 108 | url = get_volume_url() 109 | if url is None: 110 | return 111 | INFO("getting audio volume: %s" % url) 112 | response = None 113 | try: 114 | response = urllib2.urlopen(url).read() 115 | except urllib2.HTTPError, e: 116 | INFO(e) 117 | WARN("audio server address is invaild") 118 | except urllib2.URLError, e: 119 | INFO(e) 120 | WARN("audio server unavailable.") 121 | else: 122 | INFO("audio response: " + response) 123 | INFO("getting audio volume: %s" % response) 124 | return response 125 | 126 | def clear_queue(): 127 | url = get_clear_request_url() 128 | if url is None: 129 | return 130 | INFO("cleaning audio queue") 131 | try: 132 | response = urllib2.urlopen(url).read() 133 | except urllib2.HTTPError, e: 134 | INFO(e) 135 | WARN("audio server address is invaild") 136 | except urllib2.URLError, e: 137 | INFO(e) 138 | WARN("audio server unavailable.") 139 | else: 140 | INFO("audio response: " + response) 141 | -------------------------------------------------------------------------------- /lib/sound/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/lib/sound/__init__.py -------------------------------------------------------------------------------- /lib/speech/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/lib/speech/__init__.py -------------------------------------------------------------------------------- /log_home.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tail -n 100 -f log/home.log 3 | -------------------------------------------------------------------------------- /mqtt_server_proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import argparse 20 | import threading 21 | import time 22 | import json 23 | import subprocess 24 | import urllib, urllib2 25 | import os 26 | import base64 27 | import errno 28 | from datetime import datetime 29 | 30 | import paho.mqtt.client as mqtt 31 | 32 | from util.Res import Res 33 | from util.log import * 34 | 35 | 36 | class mqtt_server_proxy: 37 | 38 | NO_HEAD_FLAG = "*" 39 | BASE64_SUB_KEY = "/lehome/base64" 40 | MESSAGE_DIRECTORY = "./usr/message/" 41 | 42 | def __init__(self, address): 43 | if not address is None: 44 | INFO("connect to home: %s " % (address)) 45 | self._home_address = address 46 | 47 | settings = Res.init("init.json") 48 | self._device_id = settings['id'].encode("utf-8") 49 | self._server_addr = settings['connection']['mqtt_server'].encode("utf-8") 50 | self._trigger_cmd = settings['command']['trigger'][0].encode("utf-8") 51 | self._finish_cmd = settings['command']['finish'][0].encode("utf-8") 52 | INFO("load device id:%s" % self._device_id) 53 | else: 54 | ERROR("address is empty") 55 | 56 | def _send_cmd_to_home(self, cmd): 57 | if not cmd is None and not cmd == "": 58 | DEBUG("send cmd %s to home." % (cmd, )) 59 | if cmd.startswith(mqtt_server_proxy.NO_HEAD_FLAG): 60 | cmd = cmd[1:] 61 | else: 62 | cmd = "%s%s%s" % (self._trigger_cmd, cmd, self._finish_cmd) 63 | 64 | try: 65 | data = {"cmd": cmd} 66 | enc_data = urllib.urlencode(data) 67 | response = urllib2.urlopen(self._home_address, 68 | enc_data, 69 | timeout=5).read() 70 | except urllib2.HTTPError, e: 71 | ERROR(e) 72 | return False 73 | except urllib2.URLError, e: 74 | ERROR(e) 75 | return False 76 | except Exception, e: 77 | ERROR(e) 78 | return False 79 | else: 80 | DEBUG("home response: " + response) 81 | return True 82 | else: 83 | ERROR("cmd is invaild.") 84 | return False 85 | 86 | def start(self): 87 | self._fetch_worker() 88 | # fetch_t = threading.Thread( 89 | # target=self._fetch_worker 90 | # ) 91 | # fetch_t.daemon = True 92 | # fetch_t.start() 93 | # time.sleep(100) 94 | 95 | def _fetch_worker(self): 96 | INFO("start fetching cmds.") 97 | self._mqtt_client = mqtt.Client() 98 | self._mqtt_client.on_connect = self._on_mqtt_connect 99 | self._mqtt_client.on_message = self._on_mqtt_message 100 | self._mqtt_client.connect(self._server_addr, 1883, 60) 101 | self._mqtt_client.loop_forever() 102 | 103 | def _on_mqtt_connect(self, client, userdata, flags, rc): 104 | print("mqtt server connected with result code "+str(rc)) 105 | subscribables = [ 106 | (self._device_id, 1), 107 | (self._device_id + mqtt_server_proxy.BASE64_SUB_KEY, 1) 108 | ] 109 | client.subscribe(subscribables) 110 | 111 | def _on_mqtt_message(self, client, userdata, msg): 112 | payload = str(msg.payload) 113 | print(msg.topic + " " + payload) 114 | if payload is not None and len(payload) != 0: 115 | if msg.topic == self._device_id: 116 | DEBUG("sending payload to home:%s" % payload) 117 | try: 118 | self._send_cmd_to_home(payload) 119 | except Exception, ex: 120 | import traceback 121 | traceback.format_exc() 122 | print "exception in _on_mqtt_message, normal topic:", ex 123 | elif msg.topic == self._device_id + mqtt_server_proxy.BASE64_SUB_KEY: 124 | try: 125 | datas = json.loads(payload, strict=False) 126 | self._handle_base64_payload(datas) 127 | except Exception, ex: 128 | import traceback 129 | traceback.format_exc() 130 | print "exception in _on_mqtt_message, message topic", ex 131 | 132 | def _handle_base64_payload(self, datas): 133 | mtype = datas["type"] 134 | if mtype == "message": 135 | try: 136 | audio_data = base64.b64decode(datas["payload"]) 137 | file_name = datas["filename"] 138 | if file_name is None or len(file_name) == 0: 139 | ERROR("no filename for message base64 request") 140 | return 141 | path = mqtt_server_proxy.MESSAGE_DIRECTORY 142 | try: 143 | os.makedirs(path) 144 | except OSError as exc: 145 | if exc.errno == errno.EEXIST and os.path.isdir(path): 146 | pass 147 | else: 148 | ERROR(exc) 149 | return 150 | 151 | filepath = path + datetime.now().strftime("%m-%d-%H-%M-%S") + ".spx" 152 | # filepath = path + file_name 153 | with open(filepath, "wb") as f: 154 | f.write(audio_data) 155 | INFO("finish writing message file:%s, now send it to home." % filepath) 156 | 157 | self._send_cmd_to_home(u"显示#你有新留言#".encode("utf-8")) 158 | self._send_cmd_to_home(u"播放留言最新".encode("utf-8")) 159 | 160 | except Exception, ex: 161 | ERROR(ex) 162 | ERROR("decoding message error") 163 | else: 164 | print "unknown payload type in base64" 165 | 166 | if __name__ == "__main__": 167 | parser = argparse.ArgumentParser( 168 | description='mqtt_server_proxy.py -a address') 169 | parser.add_argument('-a', 170 | action="store", 171 | dest="address", 172 | default="http://localhost:8000/home/cmd", 173 | ) 174 | args = parser.parse_args() 175 | address = args.address 176 | 177 | INFO("mqtt server proxy is activate.") 178 | mqtt_server_proxy(address).start() 179 | -------------------------------------------------------------------------------- /ping_endpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2010 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import threading 20 | import os 21 | import signal 22 | import time 23 | import subprocess 24 | import errno 25 | import json 26 | from Queue import Queue, Empty 27 | import collections 28 | import logging 29 | 30 | import zmq 31 | import ping 32 | 33 | from util.log import * 34 | from util.Res import Res 35 | 36 | 37 | class ping_endpoint(object): 38 | 39 | PING_INTERVAL = 10 40 | TIMEOUT = 0.8 41 | 42 | def __init__(self): 43 | self.devices = {} 44 | self._queues = {} 45 | 46 | settings = Res.init("init.json") 47 | self.server_ip = "tcp://*:8005" 48 | self._device_addrs = [] 49 | addrs = settings['ping']['device'] 50 | for name in addrs: 51 | self._device_addrs.append(addrs[name]) 52 | 53 | def _do_ping(self, addr): 54 | while True: 55 | try: 56 | self.devices[addr] = self._ping(addr) 57 | print "device:", addr, "online", self.devices[addr] 58 | DEBUG("device:%s online:%d" % (addr, self.devices[addr])) 59 | 60 | time.sleep(ping_endpoint.PING_INTERVAL) 61 | except (KeyboardInterrupt, SystemExit): 62 | self.stop() 63 | raise 64 | except Exception, ex: 65 | ERROR(ex) 66 | TRACE_EX() 67 | time.sleep(3) 68 | 69 | def _timeout(self, proc): 70 | print "timeout!" 71 | if proc.poll() is None: 72 | try: 73 | proc.terminate() 74 | INFO('Error: process taking too long to complete--terminating') 75 | except OSError as e: 76 | if e.errno != errno.ESRCH: 77 | raise 78 | 79 | def _ping(self, addr): 80 | cmd = "ping -c %d -W %.1f %s > /dev/null 2>&1" % (10, ping_endpoint.TIMEOUT, addr) 81 | proc_timeout = 10*ping_endpoint.TIMEOUT + 5 82 | proc = subprocess.Popen(cmd, shell=True) 83 | 84 | try: 85 | t = threading.Timer(proc_timeout, self._timeout, [proc]) 86 | t.start() 87 | proc.wait() 88 | DEBUG("ping return code:%d" % proc.returncode) 89 | t.cancel() 90 | t.join() 91 | except Exception, ex: 92 | print ex 93 | proc.terminate() 94 | return True if proc.returncode == 0 else False 95 | 96 | def start(self): 97 | 98 | INFO("start ping server...") 99 | INFO("bind to" + self.server_ip) 100 | 101 | for addr in self._device_addrs: 102 | INFO("load ping device:" + addr) 103 | self.devices[addr] = False 104 | ping_t = threading.Thread( 105 | target=self._do_ping, 106 | args=(addr, ) 107 | ) 108 | ping_t.daemon = True 109 | ping_t.start() 110 | 111 | context = zmq.Context() 112 | poller = zmq.Poller() 113 | self.socket = None 114 | time.sleep(0.5) 115 | 116 | while True: 117 | if self.socket is None: 118 | self.socket = context.socket(zmq.REP) 119 | self.socket.setsockopt(zmq.LINGER, 0) 120 | self.socket.bind(self.server_ip) 121 | poller.register(self.socket, zmq.POLLIN | zmq.POLLOUT) 122 | try: 123 | if poller.poll(5*1000): 124 | req = self.socket.recv_string() 125 | rep = {"res": "error"} 126 | if req in self._device_addrs: 127 | state = self.devices[req] 128 | rep = {"res": 129 | { 130 | "online": state, 131 | } 132 | } 133 | self.socket.send_string(json.dumps(rep)) 134 | DEBUG("recv req:" + str(req)) 135 | except (KeyboardInterrupt, SystemExit): 136 | self.socket.close() 137 | poller.unregister(self.socket) 138 | self.socket = None 139 | self.stop() 140 | raise 141 | except Exception, ex: 142 | ERROR(ex) 143 | self.socket.close() 144 | poller.unregister(self.socket) 145 | self.socket = None 146 | time.sleep(3) 147 | 148 | def stop(self): 149 | INFO("ping_endpoint stop.") 150 | 151 | if __name__ == '__main__': 152 | ping_endpoint().start() 153 | -------------------------------------------------------------------------------- /quick_button.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2010 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import threading 20 | import os 21 | import signal 22 | import random 23 | import time 24 | import subprocess 25 | import json 26 | from Queue import Queue, Empty 27 | import collections 28 | import urllib, urllib2 29 | 30 | import pygame 31 | 32 | from util.log import * 33 | import RPi.GPIO as GPIO 34 | 35 | GPIO.setmode(GPIO.BOARD) 36 | 37 | class RemoteButtonController(object): 38 | def __init__(self, state_queue): 39 | self.input_pin = [13, 15, 16, 18] 40 | self.mapping_btn = {13:"C", 15:"B", 16:"A", 18:"D"} 41 | self._state_queue = state_queue 42 | self.setup() 43 | 44 | def delay(self, ms): 45 | time.sleep(1.0*ms/1000) 46 | 47 | def beep(self): 48 | if self._beep is not None: 49 | self._beep.play() 50 | 51 | def setup(self): 52 | for pin in self.input_pin: 53 | GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) 54 | GPIO.add_event_detect( 55 | pin, 56 | GPIO.RISING, 57 | callback=self._interupt, 58 | bouncetime=50 59 | ) 60 | 61 | pygame.init() 62 | try: 63 | self._beep = pygame.mixer.Sound("./usr/res/com_btn2.wav") 64 | except Exception, ex: 65 | print "quick button beep init error!" 66 | ERROR("quick button beep init error!") 67 | self._beep = None 68 | 69 | def _interupt(self, pin): 70 | if self._state_queue is not None: 71 | if pin in self.mapping_btn: 72 | INFO("interrupt: %s" % self.mapping_btn[pin]) 73 | self.beep() 74 | self._state_queue.put(self.mapping_btn[pin]) 75 | 76 | def cleanup(self): 77 | GPIO.setmode(GPIO.BOARD) 78 | for pin in self.input_pin: 79 | GPIO.remove_event_detect(pin) 80 | # GPIO.cleanup() 81 | 82 | class quick_button(object): 83 | 84 | def __init__(self): 85 | self._event_queue = Queue() 86 | self._configure(conf_path="./usr/btn_conf.json") 87 | self._init_params() 88 | self._btn_ctler = RemoteButtonController(self._event_queue) 89 | 90 | def _configure(self, conf_path): 91 | with open(conf_path) as f: 92 | conf_json = json.load(f) 93 | if not conf_json: 94 | ERROR("error: invaild btn_conf.json") 95 | return 96 | self._conf = conf_json 97 | 98 | def _init_params(self): 99 | self.server_ip = self._conf["server_ip"] 100 | self._timeout = self._conf["timeout"] 101 | self._trigger_cmd = self._conf['trigger'].encode("utf-8") 102 | self._finish_cmd = self._conf['finish'].encode("utf-8") 103 | self._reset_cmd = self._conf['reset'] 104 | self._cmds = {} 105 | self._direct_key = {} 106 | for key in self._conf["command"]: 107 | value = self._conf["command"][key] 108 | if key.endswith("*"): 109 | key = key[:-1] 110 | self._direct_key[key] = value 111 | # print key, value 112 | else: 113 | self._cmds[key] = value 114 | 115 | def reload_cmds(self): 116 | self._configure(conf_path="./usr/btn_conf.json") 117 | self._init_params() 118 | 119 | def _is_event_vaild(self, event): 120 | if event is None: 121 | return False 122 | return True 123 | 124 | def _is_reset_cmd(self, cmd): 125 | return cmd == self._reset_cmd 126 | 127 | def _event_consume_worker(self): 128 | cmd_buf = [] 129 | while True: 130 | try: 131 | event = self._event_queue.get(timeout=self._timeout) 132 | print "got event: ", event 133 | self._event_queue.task_done() 134 | if len(cmd_buf) == 0 and event in self._direct_key: 135 | self._map_buffer_to_command(event) 136 | else: 137 | cmd_buf.extend(event) 138 | except Empty: 139 | if len(cmd_buf) > 0: 140 | print "timeout:", cmd_buf 141 | DEBUG('dequeue event timeout. now collecting buffer.') 142 | cmd = "".join(cmd_buf) 143 | if self._is_reset_cmd(cmd): 144 | INFO("reload cmds.") 145 | self.reload_cmds() 146 | else: 147 | self._map_buffer_to_command(cmd) 148 | del cmd_buf[:] 149 | except (KeyboardInterrupt, SystemExit): 150 | self.stop() 151 | raise 152 | except Exception, ex: 153 | ERROR(ex) 154 | time.sleep(1) 155 | 156 | def _map_buffer_to_command(self, cmd): 157 | if cmd in self._direct_key: 158 | self._send_cmd_to_home(self._direct_key[cmd].encode("utf-8")) 159 | elif cmd in self._cmds: 160 | self._send_cmd_to_home(self._cmds[cmd].encode("utf-8")) 161 | 162 | def _send_cmd_to_home(self, cmd): 163 | print "find mapping! send to home:", cmd 164 | # pass 165 | if not cmd is None and not cmd == "": 166 | INFO("send cmd %s to home." % (cmd, )) 167 | cmd = "%s%s%s" % (self._trigger_cmd, cmd, self._finish_cmd) 168 | 169 | try: 170 | data = {"cmd": cmd} 171 | enc_data = urllib.urlencode(data) 172 | response = urllib2.urlopen(self.server_ip, 173 | enc_data, 174 | timeout=5).read() 175 | except urllib2.HTTPError, e: 176 | ERROR(e) 177 | return False 178 | except urllib2.URLError, e: 179 | ERROR(e) 180 | return False 181 | else: 182 | INFO("home response: " + response) 183 | return True 184 | else: 185 | ERROR("cmd is invaild.") 186 | return False 187 | 188 | def start(self): 189 | INFO("start quick_button server...") 190 | INFO("command server ip:" + self.server_ip) 191 | 192 | self._event_consume_worker() 193 | 194 | def stop(self): 195 | INFO("quick_button stop.") 196 | self._btn_ctler.cleanup() 197 | 198 | if __name__ == '__main__': 199 | qb = quick_button() 200 | try: 201 | qb.start() 202 | finally: 203 | qb.stop() 204 | INFO("clean GPIO. now exit") 205 | print "exit." 206 | -------------------------------------------------------------------------------- /remote_info_sender.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import argparse 20 | import threading 21 | import traceback 22 | import time 23 | from Queue import Queue, Empty 24 | import urllib, urllib2 25 | import json 26 | import hashlib 27 | 28 | import zmq 29 | 30 | from util.Res import Res 31 | from util.log import * 32 | from vendor.baidu_push.Channel import * 33 | from vendor.xg_push import xinge 34 | from vendor.mipush import mipush 35 | 36 | PUSH_apiKey = "7P5ZCG6WTAGWr5TuURBgndRH" 37 | PUSH_secretKey = "gggk30ubCSFGM5uXYfwGll4vILlnQ0em" 38 | PUSH_user_id = "4355409" 39 | 40 | XINGE_ACCESS_ID = 2100063377 41 | XINGE_SECRET_KEY = "50618a8882f2dd7849a2f2bc68f41587" 42 | 43 | MIPUSH_SECRET_KEY = "3b6uuQ2wE0ox80Tv4kV2fw==" 44 | MIPUSH_PACKETAGE = "my.home.lehome" 45 | 46 | class remote_info_sender: 47 | 48 | HOST = "http://lehome.sinaapp.com" 49 | 50 | def __init__(self, address): 51 | if not address is None: 52 | INFO("connect to server: %s " % (address)) 53 | context = zmq.Context() 54 | self._sock = context.socket(zmq.SUB) 55 | self._sock.connect(address) 56 | self._sock.setsockopt(zmq.SUBSCRIBE, '') 57 | self.channel = Channel(PUSH_apiKey, PUSH_secretKey) 58 | self._mipush = mipush.MIPush(MIPUSH_SECRET_KEY) 59 | self._msg_queue = Queue() 60 | 61 | settings = Res.init("init.json") 62 | self._device_id = settings['id'] 63 | else: 64 | ERROR("address is empty") 65 | 66 | def _send_info_to_server(self, info): 67 | if not info is None and not info == "": 68 | DEBUG("send info %s to remote server." % (info, )) 69 | try: 70 | info = info.encode('utf-8') 71 | rep = self._sae_info(info) 72 | if len(rep) != 0: 73 | DEBUG("remote_server rep:%s" % rep) 74 | obj_rep = json.loads(rep) 75 | if obj_rep['code'] == 200: 76 | return True 77 | else: 78 | ERROR("send info error code:%d, desc:%s" % 79 | (obj_rep['code'], obj_rep['desc'])) 80 | except Exception, e: 81 | ERROR(e) 82 | return False 83 | else: 84 | ERROR("info is invaild.") 85 | return False 86 | 87 | def _push_info_baidu(self, info, tag_name): 88 | if not info is None and not info == "": 89 | DEBUG("baidu push info %s to remote server." % (info, )) 90 | # baidu push 91 | push_type = 2 92 | optional = {} 93 | optional[Channel.TAG_NAME] = tag_name 94 | try: 95 | ret = self.channel.pushMessage(push_type, info, "key", optional) 96 | DEBUG("baidu push ret:%s" % ret) 97 | except Exception, e: 98 | ERROR(e) 99 | return False 100 | return True 101 | else: 102 | ERROR("info is invaild.") 103 | return False 104 | 105 | def _push_info_xg(self, info, tag_name): 106 | if not info is None and not info == "": 107 | DEBUG("xg push info %s to remote server." % (info, )) 108 | # xg push 109 | msg = self._build_msg(info) 110 | if msg is None: 111 | ERROR("xg msg is None.") 112 | return False 113 | try: 114 | # DEBUG("xg msg len: %d" % len(msg.content)) 115 | ret = self.xinge_app.PushTags(0, (tag_name,), "OR", msg) 116 | DEBUG("xg push ret: %s,%s" % (ret[0], ret[1])) 117 | except Exception, e: 118 | ERROR(e) 119 | return False 120 | return True 121 | else: 122 | ERROR("info is invaild.") 123 | return False 124 | 125 | def _build_msg(self, content): 126 | msg = xinge.Message() 127 | msg.type = xinge.Message.TYPE_MESSAGE 128 | msg.content = content 129 | msg.expireTime = 5*3600 130 | # 自定义键值对,key和value都必须是字符串 131 | # msg.custom = {'aaa':'111', 'bbb':'222'} 132 | if len(msg.content) > 2500: 133 | INFO("xg msg reach max len:%d" % len(msg.content)) 134 | try: 135 | msg_json = json.loads(msg.content) 136 | msg_id = hashlib.sha1(msg.content).hexdigest() 137 | msg_data = urllib.urlencode({'msg':msg_json["msg"].encode('utf-8')}) 138 | 139 | url = remote_info_sender.HOST + "/api/text/%s?id=%s" \ 140 | % (msg_id, self._device_id,) 141 | rep = urllib2.urlopen(url, msg_data, timeout=10).read() 142 | rep_json = json.loads(rep) 143 | if rep_json["code"] != 200: 144 | ERROR("xg api text rep code: %s" % rep_json["desc"]) 145 | return None 146 | 147 | INFO("api text success: %s", msg_id) 148 | msg_json["type"] = "long_msg" 149 | msg_json["msg"] = url 150 | msg.content = json.dumps(msg_json) 151 | except Exception, e: 152 | ERROR(traceback.format_exc()) 153 | return None 154 | return msg 155 | 156 | def _push_info_xiaomi(self, info, tag_name): 157 | if not info is None and not info == "": 158 | DEBUG("xiaomi push info %s to remote server." % (info, )) 159 | # xiaomi push 160 | try: 161 | ret = self._mipush.push_topic_passthrough(info, MIPUSH_PACKETAGE, tag_name) 162 | DEBUG("mi push ret:%s" % ret) 163 | except Exception, e: 164 | ERROR(e) 165 | return False 166 | return True 167 | else: 168 | ERROR("info is invaild.") 169 | return False 170 | 171 | def _sae_info(self, info): 172 | url = remote_info_sender.HOST + "/info/put?id=%s" \ 173 | % (self._device_id,) 174 | rep = urllib2.urlopen(url, info, timeout=10).read() 175 | return rep 176 | 177 | def _put_msg(self, msg): 178 | self._msg_queue.put(msg) 179 | 180 | def _get_msg(self): 181 | msg = self._msg_queue.get( 182 | block=True, 183 | ) # block! 184 | self._msg_queue.task_done() 185 | return msg 186 | 187 | def start(self): 188 | if self._msg_queue is None: 189 | ERROR("remote_info_sender start faild.") 190 | return 191 | 192 | send_t = threading.Thread( 193 | target=self._send_worker 194 | ) 195 | send_t.daemon = True 196 | send_t.start() 197 | self._put_worker() 198 | 199 | def _send_worker(self): 200 | while True: 201 | info = self._get_msg() 202 | DEBUG("_send_worker get: %s" % info) 203 | if not "\"heartbeat\"" in info: 204 | # info_object = json.loads(info) 205 | # msg_type = info_object['type'] 206 | # if msg_type != "heartbeat": 207 | # self._push_info_xg(info, str(self._device_id)) 208 | # self._push_info_baidu(info, str(self._device_id)) 209 | self._push_info_xiaomi(info, str(self._device_id)) 210 | self._send_info_to_server(info) 211 | 212 | def _put_worker(self): 213 | INFO("start waiting infos from home.") 214 | while True : 215 | try: 216 | info = self._sock.recv_string() 217 | self._put_msg(info) 218 | DEBUG("get info from home:%s" % info) 219 | except (KeyboardInterrupt, SystemExit): 220 | raise 221 | except Exception, ex: 222 | ERROR(ex) 223 | 224 | 225 | if __name__ == "__main__": 226 | parser = argparse.ArgumentParser( 227 | description='remote_info_sender.py -a address') 228 | parser.add_argument('-a', 229 | action="store", 230 | dest="address", 231 | default="tcp://localhost:9000", 232 | ) 233 | args = parser.parse_args() 234 | address = args.address 235 | 236 | INFO("remote info sender is activate.") 237 | remote_info_sender(address).start() 238 | -------------------------------------------------------------------------------- /remote_server_proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import argparse 20 | import threading 21 | import time 22 | import json 23 | import urllib, urllib2 24 | from util.Res import Res 25 | from util.log import * 26 | 27 | 28 | class remote_server_proxy: 29 | 30 | HOST = "http://lehome.sinaapp.com" 31 | NO_HEAD_FLAG = "*" 32 | CMD_FETCH_INTERVAL = 3 33 | 34 | def __init__(self, address): 35 | if not address is None: 36 | INFO("connect to server: %s " % (address)) 37 | self._home_address = address 38 | 39 | settings = Res.init("init.json") 40 | self._device_id = settings['id'] 41 | self._trigger_cmd = settings['command']['trigger'][0].encode("utf-8") 42 | self._finish_cmd = settings['command']['finish'][0].encode("utf-8") 43 | INFO("load device id:%s" % self._device_id) 44 | else: 45 | ERROR("address is empty") 46 | 47 | def _send_cmd_to_home(self, cmd): 48 | if not cmd is None and not cmd == "": 49 | INFO("send cmd %s to home." % (cmd, )) 50 | if cmd.startswith(remote_server_proxy.NO_HEAD_FLAG): 51 | cmd = cmd[1:] 52 | else: 53 | cmd = "%s%s%s" % (self._trigger_cmd, cmd, self._finish_cmd) 54 | 55 | try: 56 | data = {"cmd": cmd} 57 | enc_data = urllib.urlencode(data) 58 | response = urllib2.urlopen(self._home_address, 59 | enc_data, 60 | timeout=5).read() 61 | except urllib2.HTTPError, e: 62 | ERROR(e) 63 | return False 64 | except urllib2.URLError, e: 65 | ERROR(e) 66 | return False 67 | else: 68 | INFO("home response: " + response) 69 | return True 70 | else: 71 | ERROR("cmd is invaild.") 72 | return False 73 | 74 | def start(self): 75 | self._fetch_worker() 76 | # fetch_t = threading.Thread( 77 | # target=self._fetch_worker 78 | # ) 79 | # fetch_t.daemon = True 80 | # fetch_t.start() 81 | 82 | def _fetch_worker(self): 83 | INFO("start fetching cmds from remote server.") 84 | while True : 85 | try: 86 | DEBUG("sending fetch request to remote server.") 87 | url = remote_server_proxy.HOST + "/cmd/fetch?id=" + self._device_id 88 | req = urllib2.Request(url) 89 | cmds = urllib2.urlopen(req, timeout=10).read() 90 | obj_cmds = json.loads(cmds) 91 | rep_code = obj_cmds['code'] 92 | if rep_code == 200 or rep_code == 201: 93 | for cmd in obj_cmds['data']: 94 | INFO("fetch cmds:%s" % cmd) 95 | # tornado handler needs encode utf-8 96 | self._send_cmd_to_home(cmd.encode('utf-8')) 97 | else: 98 | WARN("fetch cmds error code %d, desc:%s" 99 | % (obj_cmds['code'], obj_cmds['desc'])) 100 | except urllib2.URLError, e: 101 | WARN(e) 102 | except Exception, ex: 103 | ERROR(ex) 104 | time.sleep(remote_server_proxy.CMD_FETCH_INTERVAL) 105 | WARN("fetch worker exit!") 106 | self._sock_context.term() 107 | 108 | 109 | if __name__ == "__main__": 110 | parser = argparse.ArgumentParser( 111 | description='remote_server_proxy.py -a address') 112 | parser.add_argument('-a', 113 | action="store", 114 | dest="address", 115 | default="http://localhost:8000/home/cmd", 116 | ) 117 | args = parser.parse_args() 118 | address = args.address 119 | 120 | INFO("remote server proxy is activate.") 121 | remote_server_proxy(address).start() 122 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 4 | cd $DIR 5 | 6 | mkdir -p log 7 | 8 | echo 'running tag_endpoint.py' 9 | python tag_endpoint.py > /dev/null 2>&1 & 10 | rc=$? 11 | if [[ $rc != 0 ]] ; then 12 | exit $rc 13 | fi 14 | 15 | echo 'running ping_endpoint.py' 16 | sudo python ping_endpoint.py > /dev/null 2>&1 & 17 | rc=$? 18 | if [[ $rc != 0 ]] ; then 19 | exit $rc 20 | fi 21 | 22 | echo 'running audio server.py...' 23 | sudo python audio_server.py > /dev/null 2>&1 & 24 | rc=$? 25 | if [[ $rc != 0 ]] ; then 26 | exit $rc 27 | fi 28 | 29 | echo 'running home.py...' 30 | python home.py > /dev/null 2>&1 & 31 | rc=$? 32 | if [[ $rc != 0 ]] ; then 33 | exit $rc 34 | fi 35 | 36 | # echo 'running remote server proxy.py' 37 | # python remote_server_proxy.py > log/remote_server.log 2>&1 & 38 | # rc=$? 39 | # if [[ $rc != 0 ]] ; then 40 | # exit $rc 41 | # fi 42 | 43 | echo 'running mqtt server proxy.py' 44 | file='log/mqtt_server.log' 45 | if [[ ! -f $file ]]; then 46 | touch $file 47 | fi 48 | python mqtt_server_proxy.py > log/mqtt_server.log 2>&1 & 49 | rc=$? 50 | if [[ $rc != 0 ]] ; then 51 | exit $rc 52 | fi 53 | 54 | echo 'running geo_fencing_server.py' 55 | file='log/geo_fencing.log' 56 | if [[ ! -f $file ]]; then 57 | touch $file 58 | fi 59 | 60 | sleep 3 # wait for home.py to init. 61 | 62 | python geo_fencing_server.py > log/geo_fencing.log 2>&1 & 63 | rc=$? 64 | if [[ $rc != 0 ]] ; then 65 | exit $rc 66 | fi 67 | 68 | if [[ $1 != "silent" ]] 69 | then 70 | echo 'running remote info sender.py' 71 | file='log/remote_proxy.log' 72 | if [[ ! -f $file ]]; then 73 | touch $file 74 | fi 75 | python remote_info_sender.py > log/remote_proxy.log 2>&1 & 76 | rc=$? 77 | if [[ $rc != 0 ]] ; then 78 | exit $rc 79 | fi 80 | fi 81 | # echo 'running sensor_server.py...' 82 | # # python sensor_server.py > log/sensor.log 2>&1 & 83 | # python sensor_server.py > /dev/null 2>&1 & 84 | # rc=$? 85 | # if [[ $rc != 0 ]] ; then 86 | # exit $rc 87 | # fi 88 | 89 | echo 'running qqfm.py...' 90 | cd ../qqfm/ 91 | file='log/qqfm.log' 92 | if [[ ! -f $file ]]; then 93 | touch $file 94 | fi 95 | sudo python qqfm.py > log/qqfm.log 2>&1 & 96 | cd ../LEHome/ 97 | rc=$? 98 | if [[ $rc != 0 ]] ; then 99 | exit $rc 100 | fi 101 | 102 | echo 'running quick_button.py' 103 | sudo python quick_button.py > /dev/null 2>&1 & 104 | rc=$? 105 | if [[ $rc != 0 ]] ; then 106 | exit $rc 107 | fi 108 | 109 | echo 'LEHome started.' 110 | -------------------------------------------------------------------------------- /stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # echo 'kill remote_server_proxy.py...' ps -ef | awk '/python remote_server_proxy\.py/ {print $2}' | xargs kill -9 4 | 5 | echo 'kill mqtt_server_proxy.py...' 6 | ps -ef | awk '/python mqtt_server_proxy\.py/ {print $2}' | xargs kill -9 7 | 8 | echo 'kill geo_fencing_server.py' 9 | ps -ef | awk '/python geo_fencing_server\.py/ {print $2}' | xargs kill -9 10 | 11 | echo 'kill remote_info_sender.py...' 12 | ps -ef | awk '/python remote_info_sender\.py/ {print $2}' | xargs kill -9 13 | 14 | echo 'kill home.py...' 15 | ps -ef | awk '/python home\.py/ {print $2}' | xargs kill -9 16 | 17 | echo 'kill audio_server.py...' 18 | ps -ef | awk '/sudo python audio_server\.py/ {print $2}' | sudo xargs kill -INT 19 | 20 | echo 'kill tag_endpoint.py' 21 | ps -ef | awk '/python tag_endpoint\.py/ {print $2}' | xargs kill -9 22 | 23 | echo 'kill ping_endpoint.py' 24 | ps -ef | awk '/python ping_endpoint\.py/ {print $2}' | sudo xargs kill -9 25 | 26 | echo 'kill qqfm.py...' 27 | ps -ef | awk '/sudo python qqfm\.py/ {print $2}' | sudo xargs kill -INT 28 | 29 | echo 'kill quick_button.py' 30 | ps -ef | awk '/python quick_button\.py/ {print $2}' | sudo xargs kill -9 31 | 32 | s2t_pid=`ps -ef | awk '/python s2t_server\.py/ {print $2}'` 33 | if [[ "$s2t_pid" ]] 34 | then 35 | echo 'kill s2t_server.py...' 36 | kill -9 $s2t_pid 37 | fi 38 | 39 | echo 'LEHome stopped.' 40 | -------------------------------------------------------------------------------- /tag_endpoint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | # Copyright 2010 Xinyu, He 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | 19 | import threading 20 | import os 21 | import signal 22 | import time 23 | import subprocess 24 | import json 25 | from Queue import Queue, Empty 26 | import collections 27 | import logging 28 | 29 | import zmq 30 | import paho.mqtt.client as mqtt 31 | 32 | from util.log import * 33 | 34 | 35 | class tag_endpoint(object): 36 | 37 | TAG_TIMEOUT = 5 38 | 39 | # tag 的蓝牙地址 40 | tag_addrs = [ 41 | "E2C56DB5DFFB48D2B060D0F5A71096E0", 42 | "FDA50693A4E24FB1AFCFC6EB07647825" 43 | ] 44 | 45 | sniffer_ids = [ 46 | "F4B85E03F44D", 47 | ] 48 | 49 | def __init__(self): 50 | self.server_ip = "tcp://*:8006" 51 | self.tags = {} 52 | self._queues = {} 53 | self._filter = {} 54 | self._init_filter() 55 | 56 | def _init_filter(self): 57 | N = 1.0 # 划动平均 58 | A = 1.0 59 | for sniffer_id in tag_endpoint.sniffer_ids: 60 | sniffer = {} 61 | for tag_addr in tag_endpoint.tag_addrs: 62 | tag = {} 63 | tag['N'] = N 64 | tag['A'] = A 65 | tag['S'] = tag['N']*tag['A'] 66 | tag['queue'] = collections.deque([255]*10, maxlen=10) 67 | sniffer[tag_addr] = tag 68 | self._filter[sniffer_id] = sniffer 69 | 70 | # 网上找的一个计算距离的公式,不太准 71 | def calDistance(self, txPower, rssi): 72 | if rssi == 0: 73 | return 0.0 74 | 75 | ratio = rssi*1.0/txPower 76 | if ratio < 1.0: 77 | return ratio**10 78 | else: 79 | accuracy = 0.89976*(ratio**7.7095) + 0.111 80 | return accuracy 81 | 82 | # 窗口长度为10,去除最高和最低值后,减去上一次的平均值再加上窗口的平均值 83 | def rssi_filter(self, sniffer_id, addr, rssi): 84 | if sniffer_id not in self._filter: 85 | return -1 86 | 87 | sniffer = self._filter[sniffer_id] 88 | if addr not in sniffer or sniffer[addr] is None: 89 | return -1 90 | 91 | tag = sniffer[addr] 92 | tag['queue'].append(rssi) 93 | ordered_queue = list(tag['queue']) 94 | ordered_queue.sort() 95 | ordered_queue = ordered_queue[1:-1] 96 | C = sum(ordered_queue)/len(ordered_queue) 97 | 98 | tag['S'] = tag['S'] - tag['A'] + C 99 | tag['A'] = tag['S']/tag['N'] 100 | return tag['A'] 101 | 102 | def _on_sniffer_connect(self, client, userdata, flags, rc): 103 | # print("Connected with result code "+str(rc)) 104 | client.subscribe("/beacons") 105 | 106 | def _on_rssi_message(self, client, userdata, msg): 107 | # print(msg.topic+" "+str(msg.payload)) 108 | try: 109 | data = json.loads(msg.payload) 110 | except Exception, ex: 111 | INFO(ex) 112 | return 113 | sniffer_id = data["id"] 114 | beacons = data["raw_beacons_data"].split(";") 115 | for raw in beacons: 116 | beacon = self._str_to_beacon_item(raw) 117 | if beacon is not None \ 118 | and sniffer_id in self._queues \ 119 | and beacon['uuid'] in self._queues[sniffer_id]: 120 | # 将收集的数据放进缓冲队列里 121 | self._queues[sniffer_id][beacon["uuid"]].put(beacon) 122 | print "sniffer:", sniffer_id, "beacon:", beacon 123 | DEBUG("sniffer:%s, beacon:%s" % (sniffer_id, str(beacon))) 124 | 125 | def _fetch_rssi(self): 126 | client = mqtt.Client() 127 | client.on_connect = self._on_sniffer_connect 128 | client.on_message = self._on_rssi_message 129 | client.connect("localhost", 1883, 60) 130 | client.loop_forever() 131 | 132 | def _parse_rssi(self, sniffer_id, addr): 133 | queue = self._queues[sniffer_id][addr] 134 | if sniffer_id not in self.tags: 135 | self.tags[sniffer_id] = {} 136 | tag = self.tags[sniffer_id] 137 | tag[addr] = -1.0 138 | txPower = 197 139 | while True: 140 | try: 141 | # 从缓冲队列里取出数据 142 | beacon = queue.get(timeout=tag_endpoint.TAG_TIMEOUT) 143 | queue.task_done() 144 | addr = beacon["uuid"] 145 | txPower = beacon["power"] 146 | rssi = self.rssi_filter(sniffer_id, addr, beacon["rssi"]) 147 | tag[addr] = self.calDistance(txPower, rssi) 148 | except Empty: 149 | rssi = self.rssi_filter(sniffer_id, addr, 255) 150 | tag[addr] = self.calDistance(txPower, rssi) 151 | DEBUG('parse %s timeout.' % addr) 152 | except (KeyboardInterrupt, SystemExit): 153 | self.socket.close() 154 | self.stop() 155 | raise 156 | except Exception, ex: 157 | tag[addr] = -1.0 158 | ERROR(ex) 159 | TRACE_EX() 160 | time.sleep(3) 161 | DEBUG("sniffer:%s, addr:%s, distance:%f"\ 162 | % (sniffer_id, addr, tag[addr])) 163 | print "sniffer:%s, addr:%s, distance:%f"\ 164 | % (sniffer_id, addr, tag[addr]) 165 | 166 | def _str_to_beacon_item(self, src): 167 | if src is None or len(src) == 0: 168 | return None 169 | result = {} 170 | delta = 0 171 | result["mac"] = src[:delta + 12] 172 | delta += 12 173 | result["uuid"] = src[delta:delta + 32] 174 | delta += 32 175 | result["major"] = src[delta:delta + 4] 176 | delta += 4 177 | result["minor"] = src[delta:delta + 4] 178 | delta += 4 179 | result["power"] = int(src[delta:delta + 2], 16) 180 | delta += 2 181 | result["bettery"] = int(src[delta:delta + 2], 16) 182 | delta += 2 183 | result["rssi"] = int(src[delta:delta + 2], 16) 184 | delta += 2 185 | return result 186 | 187 | def start(self): 188 | 189 | INFO("start tag server...") 190 | INFO("bind to" + self.server_ip) 191 | 192 | fetch_t = threading.Thread( 193 | target=self._fetch_rssi 194 | ) 195 | fetch_t.daemon = True 196 | fetch_t.start() 197 | 198 | # 当其中一个ibeacon广播超时的时候,要在程序里标记这个状态。若只用一个 199 | # 线程来统一处理接受到的广播包,那么对某一个ibeacon的超时检测的逻辑就 200 | # 会比较复杂。所以分开几条线程来处理,每条线程负责管理某个ibeacon的状态 201 | for sniffer_id in tag_endpoint.sniffer_ids: 202 | self._queues[sniffer_id] = {} 203 | for addr in tag_endpoint.tag_addrs: 204 | self._queues[sniffer_id][addr] = Queue() # queue for each tag 205 | parse_t = threading.Thread( 206 | target=self._parse_rssi, 207 | args=(sniffer_id, addr, ) 208 | ) 209 | parse_t.daemon = True 210 | parse_t.start() 211 | 212 | context = zmq.Context() 213 | poller = zmq.Poller() 214 | self.socket = None 215 | time.sleep(0.5) 216 | 217 | while True: 218 | if self.socket is None: 219 | self.socket = context.socket(zmq.REP) 220 | self.socket.setsockopt(zmq.LINGER, 0) 221 | self.socket.bind(self.server_ip) 222 | poller.register(self.socket, zmq.POLLIN | zmq.POLLOUT) 223 | try: 224 | if poller.poll(5*1000): 225 | req = self.socket.recv_string().split(",") 226 | rep = {"res": "error"} 227 | if req[0] in tag_endpoint.sniffer_ids \ 228 | and req[1] in tag_endpoint.tag_addrs: 229 | distance = self.tags[req[0]][req[1]] 230 | status = "unknown" if distance < 0 else "normal" 231 | rep = {"res": 232 | { 233 | "distance": distance, 234 | "status": status, 235 | } 236 | } 237 | self.socket.send_string(json.dumps(rep)) 238 | DEBUG("recv req:" + str(req)) 239 | except (KeyboardInterrupt, SystemExit): 240 | self.socket.close() 241 | self.stop() 242 | raise 243 | except Exception, ex: 244 | ERROR(ex) 245 | self.socket.close() 246 | poller.unregister(self.socket) 247 | self.socket = None 248 | time.sleep(3) 249 | 250 | def stop(self): 251 | INFO("tag_endpoint stop.") 252 | 253 | if __name__ == '__main__': 254 | tag_endpoint().start() 255 | -------------------------------------------------------------------------------- /usr/__init__.py: -------------------------------------------------------------------------------- 1 | import callbacks 2 | -------------------------------------------------------------------------------- /usr/btn_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "server_ip": "http://localhost:8000/home/cmd", 3 | "timeout": 1.5, 4 | "trigger": "你好", 5 | "finish": "谢谢", 6 | "command": { 7 | "A*": "如果当前落地灯状态等于字符#on#那么关闭落地灯否则打开落地灯", 8 | "B*": "如果当前主人房台灯状态等于字符#on#那么关闭主人房台灯否则打开主人房台灯", 9 | "C*": "如果当前阳台灯状态等于字符#on#那么关闭阳台灯否则打开阳台灯", 10 | "D": "运行qq电台", 11 | "DD": "停止qq电台", 12 | "DA": "增加音量", 13 | "DB": "减少音量" 14 | }, 15 | "reset": "DDDD" 16 | } 17 | -------------------------------------------------------------------------------- /usr/callbacks/__init__.py: -------------------------------------------------------------------------------- 1 | import delay 2 | import action 3 | import target 4 | import finish 5 | import stop 6 | import next 7 | import whiles 8 | import trigger 9 | import logical 10 | import compare 11 | -------------------------------------------------------------------------------- /usr/callbacks/action/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/action/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/action/tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | 5 | from __future__ import division 6 | from decimal import Decimal 7 | import subprocess 8 | import threading 9 | import urllib2 10 | import urllib 11 | import httplib 12 | import json 13 | import re 14 | import hashlib 15 | import base64 16 | # import zlib 17 | 18 | from lib.command.runtime import UserInput 19 | from lib.helper.CameraHelper import CameraHelper 20 | from lib.sound import Sound 21 | from util import Util 22 | from util.Res import Res 23 | from util.log import * 24 | from lib.model import Callback 25 | 26 | 27 | class timer_callback(Callback.Callback): 28 | 29 | def callback(self, cmd, action, target, msg): 30 | if msg is None: 31 | self._home.publish_msg(cmd, u"时间格式错误") 32 | return False, None 33 | 34 | if msg.endswith(u'点') or \ 35 | msg.endswith(u'分'): 36 | t = Util.gap_for_timestring(msg) 37 | elif msg.endswith(u"秒"): 38 | t = int(Util.cn2dig(msg[:-1])) 39 | elif msg.endswith(u"分钟"): 40 | t = int(Util.cn2dig(msg[:-2]))*60 41 | elif msg.endswith(u"小时"): 42 | t = int(Util.cn2dig(msg[:-2]))*60*60 43 | else: 44 | self._home.publish_msg(cmd, u"时间格式错误") 45 | return False 46 | if t is None: 47 | self._home.publish_msg(cmd, u"时间格式错误") 48 | return False, None 49 | DEBUG("thread wait for %d sec" % (t, )) 50 | self._home.publish_msg(cmd, action + target + msg) 51 | 52 | threading.current_thread().waitUtil(t) 53 | if threading.current_thread().stopped(): 54 | return False 55 | self._home.setResume(True) 56 | count = 7 57 | Sound.notice( Res.get_res_path("sound/com_bell"), True, count) 58 | self._home.setResume(False) 59 | return True 60 | 61 | 62 | class translate_callback(Callback.Callback): 63 | 64 | base_url = "http://fanyi.youdao.com/openapi.do" 65 | 66 | def callback(self, cmd, msg): 67 | if Util.empty_str(msg): 68 | cancel_flag = u"取消" 69 | finish_flag = u"完成" 70 | self._home.publish_msg( 71 | cmd 72 | , u"请输入内容, 输入\"%s\"或\"%s\"结束:" % (finish_flag, cancel_flag) 73 | , cmd_type="input" 74 | ) 75 | msg = UserInput(self._home).waitForInput( 76 | finish=finish_flag, 77 | cancel=cancel_flag) 78 | if msg is None: 79 | self._home.publish_msg(cmd, u"无翻译内容") 80 | elif len(msg) > 200: 81 | self._home.publish_msg(cmd, u"翻译内容过长(<200字)") 82 | else: 83 | try: 84 | values = { 85 | "keyfrom":"11111testt111", 86 | "key":"2125866912", 87 | "type":"data", 88 | "doctype":"json", 89 | "version":"1.1", 90 | "q":msg.encode("utf-8") 91 | } 92 | url = translate_callback.base_url + "?" + urllib.urlencode(values) 93 | res = urllib2.urlopen(url).read() 94 | res = " ".join(json.loads(res)["translation"]) 95 | self._home.publish_msg(cmd, u"翻译结果:\n" + res) 96 | except Exception, ex: 97 | ERROR("request error:", ex) 98 | self._home.publish_msg(cmd, u"翻译失败") 99 | return True 100 | return True 101 | 102 | 103 | class baidu_wiki_callback(Callback.Callback): 104 | base_url = "http://wapbaike.baidu.com" 105 | 106 | def searchWiki(self, word, time=10): 107 | value = {"word": word.encode("utf-8")} 108 | url = baidu_wiki_callback.base_url + \ 109 | "/search?" + urllib.urlencode(value) 110 | try: 111 | response = urllib2.urlopen(url, timeout=time) 112 | html = response.read().encode("utf-8") 113 | response.close() 114 | 115 | real_url = None 116 | content = None 117 | m = re.compile(r"URL=(.+)'>").search(html) 118 | if m: 119 | real_url = m.group(1) 120 | else: 121 | return None, None 122 | real_url = real_url[:real_url.index("?")] 123 | if not real_url is None: 124 | url = baidu_wiki_callback.base_url + real_url 125 | response = urllib2.urlopen(url, timeout=time) 126 | html = response.read() 127 | response.close() 128 | m = re.compile( 129 | r'

(.+)

', 130 | re.DOTALL 131 | ).search(html) 132 | if m: 133 | content = m.group(1) 134 | return Util.strip_tags(content), url 135 | else: 136 | return None, None 137 | except Exception, ex: 138 | ERROR("wiki error: ", ex) 139 | return None, None 140 | 141 | def callback(self, cmd, msg): 142 | if Util.empty_str(msg): 143 | cancel_flag = u"取消" 144 | finish_flag = u"完成" 145 | self._home.publish_msg( 146 | cmd 147 | , u"请输入内容, 输入\"%s\"或\"%s\"结束:" % (finish_flag, cancel_flag) 148 | , cmd_type="input" 149 | ) 150 | msg = UserInput(self._home).waitForInput( 151 | finish=finish_flag, 152 | cancel=cancel_flag) 153 | if not msg is None: 154 | self._home.publish_msg(cmd, u"正在搜索...") 155 | res, url = self.searchWiki(msg) 156 | if res is None: 157 | self._home.publish_msg(cmd, u"无百科内容") 158 | else: 159 | res = res.decode("utf-8") 160 | if len(res) > 140: 161 | res = res[:140] 162 | msg = u"百度百科:\n %s...\n%s" \ 163 | % (res, url) 164 | self._home.publish_msg(cmd, msg) 165 | else: 166 | self._home.publish_msg(cmd, u"无百科内容") 167 | return True 168 | 169 | 170 | class cal_callback(Callback.Callback): 171 | 172 | _ops = { 173 | u'加':'+', 174 | u'减':'-', 175 | u'乘':'*', 176 | u'除':'/', 177 | u'+':'+', 178 | u'-':'-', 179 | u'*':'*', 180 | u'/':'/', 181 | u'(':'(', 182 | u'(':'(', 183 | u')':')', 184 | u')':')', 185 | } 186 | 187 | def _parse_tokens(self, src): 188 | tokens = [] 189 | cur_t = u'' 190 | for term in src: 191 | if term in cal_callback._ops: 192 | if cur_t != u'': 193 | tokens.append(cur_t) 194 | cur_t = u'' 195 | tokens.append(term) 196 | else: 197 | cur_t += term 198 | if cur_t != u'': 199 | tokens.append(cur_t) 200 | return tokens 201 | 202 | def _parse_expression(self, tokens): 203 | expression = u'' 204 | for token in tokens: 205 | if token in cal_callback._ops: 206 | expression += cal_callback._ops[token] 207 | else: 208 | num = Util.cn2dig(token) 209 | if num is None: 210 | return None 211 | expression += str(num) 212 | res = None 213 | INFO("expression: " + expression) 214 | try: 215 | res = eval(expression) 216 | res = Decimal.from_float(res).quantize(Decimal('0.00')) 217 | except Exception, ex: 218 | ERROR("cal expression error:", ex) 219 | return res 220 | 221 | def callback(self, cmd, msg): 222 | if Util.empty_str(msg): 223 | cancel_flag = u"取消" 224 | finish_flag = u"完成" 225 | self._home.publish_msg( 226 | cmd 227 | , u"请输入公式, 输入\"%s\"或\"%s\"结束:" % (finish_flag, cancel_flag) 228 | , cmd_type="input" 229 | ) 230 | msg = UserInput(self._home).waitForInput( 231 | finish=finish_flag, 232 | cancel=cancel_flag) 233 | if msg is None: 234 | self._home.publish_msg(cmd, u"无公式内容") 235 | else: 236 | tokens = self._parse_tokens(msg) 237 | if not tokens is None: 238 | res = self._parse_expression(tokens) 239 | if not res is None: 240 | self._home.publish_msg(cmd, u"%s = %s" % (msg, str(res))) 241 | return True, res 242 | else: 243 | self._home.publish_msg(cmd, u"计算出错") 244 | return True, None 245 | else: 246 | self._home.publish_msg(cmd, u"格式有误") 247 | return True, None 248 | 249 | class camera_quickshot_callback(Callback.Callback): 250 | 251 | IMAGE_SERVER_URL = "http://lehome.sinaapp.com/image" 252 | IMAGE_HOST_URL = "http://lehome-image.stor.sinaapp.com/" 253 | 254 | def _upload_image(self, img_src, thumbnail_src): 255 | if img_src is None or len(img_src) == 0: 256 | return None, None 257 | 258 | INFO("uploading: %s %s" % (img_src, thumbnail_src)) 259 | # swift --insecure upload image data/capture/2015_05_23_001856.jpg 260 | proc = subprocess.Popen( 261 | [ 262 | "swift", 263 | "--insecure", 264 | "upload", 265 | "image", 266 | thumbnail_src, 267 | img_src 268 | ], 269 | stdout=subprocess.PIPE 270 | ) 271 | read_img = None 272 | read_thumbnail = None 273 | for i in range(2) : 274 | try: 275 | data = proc.stdout.readline().strip() #block / wait 276 | INFO("swift readline: %s" % data) 277 | if data.endswith(".thumbnail.jpg"): 278 | INFO("save to storage:%s" % data) 279 | read_thumbnail = camera_quickshot_callback.IMAGE_HOST_URL + data 280 | elif data.endswith(".jpg"): 281 | INFO("save to storage:%s" % data) 282 | read_img = camera_quickshot_callback.IMAGE_HOST_URL + data 283 | if not read_img is None and not read_thumbnail is None: 284 | return read_img, read_thumbnail 285 | except (KeyboardInterrupt, SystemExit): 286 | raise 287 | except Exception, ex: 288 | ERROR(ex) 289 | break 290 | return None, None 291 | 292 | def callback(self, cmd, msg): 293 | self._home.publish_msg(cmd, u"正在截图...") 294 | 295 | Sound.notice(Res.get_res_path("sound/com_shoot")) 296 | 297 | save_path="data/capture/" 298 | save_name, thumbnail_name = CameraHelper().take_a_photo(save_path) 299 | # for test 300 | # save_name = "2015_05_02_164052.jpg" 301 | if save_name is None: 302 | self._home.publish_msg(cmd, u"截图失败") 303 | INFO("capture faild.") 304 | return True 305 | img_url, thumbnail_url = self._upload_image( 306 | save_path + save_name, 307 | save_path + thumbnail_name, 308 | ) 309 | if img_url is None: 310 | self._home.publish_msg(cmd, u"截图失败") 311 | INFO("upload capture faild.") 312 | return True 313 | else: 314 | self._home.publish_msg( 315 | cmd, 316 | msg=img_url, 317 | cmd_type="capture" 318 | ) 319 | return True 320 | 321 | class push_info_callback(Callback.Callback): 322 | 323 | def callback(self, cmd, target, msg): 324 | if target is None or len(target) == 0: 325 | if msg is None or len(msg) == 0: 326 | self._home.publish_msg(cmd, u"请输入内容") 327 | return True, None 328 | self._home.publish_msg(cmd, msg) 329 | DEBUG("show_callback: %s" % msg) 330 | return True, msg 331 | return True, "push" 332 | -------------------------------------------------------------------------------- /usr/callbacks/compare/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/compare/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/compare/compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from util.log import * 4 | from lib.model import Callback 5 | 6 | 7 | class compare_callback(Callback.Callback): 8 | def callback(self, aValue, bValue): 9 | DEBUG("compare callback invoke.") 10 | return aValue == bValue 11 | 12 | 13 | class equal_callback(Callback.Callback): 14 | def callback(self, aValue, bValue): 15 | return aValue == bValue 16 | 17 | 18 | class greater_callback(Callback.Callback): 19 | def callback(self, aValue, bValue): 20 | return aValue > bValue 21 | 22 | 23 | class less_callback(Callback.Callback): 24 | def callback(self, aValue, bValue): 25 | return aValue < bValue 26 | 27 | 28 | class not_equal_callback(Callback.Callback): 29 | def callback(self, aValue, bValue): 30 | return not (aValue == bValue) 31 | -------------------------------------------------------------------------------- /usr/callbacks/delay/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/delay/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/delay/delay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | 19 | import threading 20 | from lib.sound import Sound 21 | from util.Res import Res 22 | from util import Util 23 | from lib.model import Callback 24 | from util.log import * 25 | 26 | 27 | class time_callback(Callback.Callback): 28 | def callback(self, 29 | delay=None, 30 | delay_time=None, 31 | action=None, 32 | trigger=None, 33 | ): 34 | DEBUG("* delay callback: %s, action: %s, target: %s" % (delay, action, target)) 35 | return True, pre_value 36 | 37 | 38 | class delay_callback(Callback.Callback): 39 | def callback(self, cmd, delay_time, action, target, msg): 40 | if delay_time is None or len(delay_time) == 0: 41 | self._home.publish_msg(cmd, u"时间格式错误") 42 | return False, None 43 | 44 | t = None 45 | if delay_time.endswith(u"秒") or delay_time.endswith(u"秒钟"): 46 | t = int(Util.cn2dig(delay_time[:-1])) 47 | elif delay_time.endswith(u"分钟"): 48 | t = int(Util.cn2dig(delay_time[:-2]))*60 49 | elif delay_time.endswith(u"小时"): 50 | t = int(Util.cn2dig(delay_time[:-2]))*60*60 51 | else: 52 | t = Util.gap_for_timestring(delay_time) 53 | if t is None: 54 | WARN("error delay_time format") 55 | self._home.publish_msg(cmd, u"时间格式错误:" + delay_time) 56 | return False, None 57 | info = delay_time + u"执行: %s%s%s" % ( 58 | Util.xunicode(action), 59 | Util.xunicode(target), 60 | Util.xunicode(msg) 61 | ) 62 | # self._home.publish_msg(cmd, info) # noise 63 | DEBUG("delay wait for %d sec" % (t, )) 64 | 65 | threading.current_thread().waitUtil(t) 66 | if threading.current_thread().stopped(): 67 | return False 68 | return True 69 | -------------------------------------------------------------------------------- /usr/callbacks/finish/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/finish/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/finish/finish.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from util.log import * 5 | from lib.model import Callback 6 | 7 | 8 | class finish_callback(Callback.Callback): 9 | def callback(self, action = None, target = None, 10 | msg = None, finish = None, 11 | pre_value = None): 12 | DEBUG("* finish callback: action: %s, target: %s, message: %s finish: %s pre_value: %s" %(action, target, msg, finish, pre_value)) 13 | return True, pre_value 14 | -------------------------------------------------------------------------------- /usr/callbacks/logical/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/logical/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/logical/logical.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from util.log import * 4 | from lib.model import Callback 5 | 6 | 7 | class logical_callback(Callback.Callback): 8 | def callback(self, aValue, bValue): 9 | DEBUG("logical callback invoke.") 10 | return aValue and bValue 11 | 12 | 13 | class and_callback(Callback.Callback): 14 | def callback(self, aValue, bValue): 15 | # import pdb 16 | # pdb.set_trace() 17 | return aValue and bValue 18 | 19 | 20 | class or_callback(Callback.Callback): 21 | def callback(self, aValue, bValue): 22 | return aValue or bValue 23 | -------------------------------------------------------------------------------- /usr/callbacks/next/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/next/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/next/next.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | 5 | from lib.model import Callback 6 | 7 | 8 | class next_callback(Callback.Callback): 9 | def callback(self, action = None, target = None, 10 | msg = None, state = None, 11 | pre_value = None, pass_value = None): 12 | DEBUG("* next callback: action: %s, target: %s, message: %s state: %s pre_value: %s pass_value %s" %(action, target, msg, state, pre_value, pass_value)) 13 | return True, "pass" 14 | -------------------------------------------------------------------------------- /usr/callbacks/stop/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/stop/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/stop/stop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from util.log import * 5 | from lib.model import Callback 6 | 7 | 8 | class stop_callback(Callback.Callback): 9 | def callback(self, stop = None): 10 | DEBUG("stop command:", stop) 11 | return True, "stop" 12 | -------------------------------------------------------------------------------- /usr/callbacks/target/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/target/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/trigger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/trigger/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/trigger/trigger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from util.log import * 5 | from lib.model import Callback 6 | 7 | class trigger_callback(Callback.Callback): 8 | def callback(self, action = None, 9 | trigger = None, 10 | pre_value = None): 11 | DEBUG("* trigger callback: %s, action: %s pre_value: %s" %(trigger, action, pre_value)) 12 | return True, True 13 | -------------------------------------------------------------------------------- /usr/callbacks/whiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/callbacks/whiles/__init__.py -------------------------------------------------------------------------------- /usr/callbacks/whiles/whiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | 5 | from lib.model import Callback 6 | 7 | 8 | class while_callback(Callback.Callback): 9 | def callback(self): 10 | return True, "while" 11 | -------------------------------------------------------------------------------- /usr/init.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"12345678", 3 | "connection":{ 4 | "cmd_bind_port" : "8000", 5 | "publisher" : "tcp://*:9000", 6 | "heartbeat_port" : "9001", 7 | "audio_server" : "http://localhost:8001", 8 | "tag_server" : "tcp://localhost:8006", 9 | "ping_server" : "tcp://localhost:8005", 10 | "mqtt_server" : "119.29.102.249", 11 | "geo_fencing_server" : "http://localhost:8009" 12 | }, 13 | "storage": { 14 | "host" : "localhost", 15 | "port" : "6379" 16 | }, 17 | "command":{ 18 | "while" : ["循环", "重复"], 19 | "if" : ["如果"], 20 | "then" : ["那么", "内容为", "内容是"], 21 | "else" : ["否则"], 22 | "delay" : ["定时", "延时"], 23 | "trigger" : ["hello", "您好", "你好"], 24 | "action" : ["打开", "开启", "关闭", "播放", "停止播放", "响起", 25 | "删除", "设置", "每", "执行", "运行", "停止", "暂停", 26 | "恢复", "减少", "降低", 27 | "结束", "显示", "查看", "新增", "新建", "添加", "增加", 28 | "逻辑", "数值", "字符", "时刻", "当前", "获取", "查询", 29 | "翻译", "百科", "计算", "计时", "截图", "推送", "触发", 30 | "绑定", "等待", "当", "定位", "后台定位", "静音"], 31 | "target" : ["豆瓣电台", "qq电台", "QQ电台", "新闻电台", "留言", 32 | "提醒", "闹铃", "铃声", "公交车", "公车", "公交站", 33 | "任务", "待办事项", "脚本", "变量", "警报", "时间", 34 | "开关", "传感器", "热水器", "电饭煲", "风扇", "阳台灯", 35 | "落地灯", "主人房台灯", 36 | "温度", "湿度", "电灯", "空调", "音量", "Github趋势", 37 | "新宇", "芳莹", 38 | "天气", "录音", "语音", "基金"], 39 | "stop" : ["算了", "取消"], 40 | "finish" : ["好吗", "谢谢"], 41 | "next" : ["然后", "接着", "的时候"], 42 | "logical" : ["而且", "并且", "或者"], 43 | "compare" : ["大于", "等于", "小于"] 44 | }, 45 | "callback":{ 46 | "whiles":{ 47 | "循环":"whiles.while_callback", 48 | "重复":"whiles.while_callback" 49 | }, 50 | "delay":{ 51 | "定时":"delay.delay_callback", 52 | "延时":"delay.delay_callback" 53 | }, 54 | "trigger":{ 55 | "hello":"trigger.trigger_callback", 56 | "您好":"trigger.trigger_callback", 57 | "你好":"trigger.trigger_callback" 58 | }, 59 | "action":{ 60 | "逻辑":"action.logical_value_callback", 61 | "数值":"action.num_value_callback", 62 | "字符":"action.str_value_callback", 63 | "时刻":"action.time_value_callback", 64 | "每":"action.every_callback", 65 | "执行":"action.invoke_callback", 66 | "运行":"action.invoke_callback", 67 | "暂停":"action.suspend_callback", 68 | "恢复":"action.resume_callback", 69 | "打开":"action.switch_on_callback", 70 | "开启":"action.switch_on_callback", 71 | "关闭":"action.switch_off_callback", 72 | "播放":"action.play_callback", 73 | "响起":"action.play_callback", 74 | "停止播放":"action.stop_play_callback", 75 | "新增":"action.new_callback", 76 | "新建":"action.new_callback", 77 | "添加":"action.new_callback", 78 | "增加":"action.new_callback", 79 | "减少":"action.lower_callback", 80 | "降低":"action.lower_callback", 81 | "删除":"action.remove_callback", 82 | "设置":"action.set_callback", 83 | "停止":"action.break_callback", 84 | "结束":"action.break_callback", 85 | "显示":"action.show_callback", 86 | "查看":"action.show_callback", 87 | "查询":"action.show_callback", 88 | "当前":"action.get_callback", 89 | "获取":"action.get_callback", 90 | "绑定":"action.hook_callback", 91 | "等待":"action.hook_callback", 92 | "当":"action.hook_callback", 93 | "定位":"action.location_callback", 94 | "后台定位":"action.geo_location_callback", 95 | "触发":"action.trigger_callback", 96 | "静音":"action.mute_callback", 97 | "翻译":"tools.translate_callback", 98 | "百科":"tools.baidu_wiki_callback", 99 | "计时":"tools.timer_callback", 100 | "计算":"tools.cal_callback", 101 | "截图":"tools.camera_quickshot_callback", 102 | "推送":"tools.push_info_callback" 103 | }, 104 | "target":{ 105 | "闹铃":"target.alarm_callback", 106 | "铃声":"target.bell_callback", 107 | "警报":"target.warning_bell_callback", 108 | "录音":"target.record_callback", 109 | "基金":"target.fund_callback", 110 | "豆瓣电台":"target.douban_callback", 111 | "qq电台":"target.qqfm_callback", 112 | "QQ电台":"target.qqfm_callback", 113 | "新闻电台":"target.newsfm_callback", 114 | "天气":"target.weather_report_callback", 115 | "留言":"target.message_callback", 116 | "公交车":"target.bus_callback", 117 | "公车":"target.bus_callback", 118 | "公交站":"target.bus_station_callback", 119 | "待办事项":"target.todo_callback", 120 | "提醒":"target.todo_callback", 121 | "脚本":"target.script_callback", 122 | "变量":"target.var_callback", 123 | "任务":"target.task_callback", 124 | "时间":"target.time_callback", 125 | "开关":"target.switch_callback", 126 | "传感器":"target.sensor_callback", 127 | "语音":"target.speech_callback", 128 | "音量":"target.volume_callback", 129 | "温度":"target.temperature_sensor_callback", 130 | "湿度":"target.humidity_sensor_callback", 131 | "热水器":"target.normal_switch_callback", 132 | "电饭煲":"target.normal_switch_callback", 133 | "风扇":"target.normal_switch_callback", 134 | "落地灯":"target.normal_switch_callback", 135 | "阳台灯":"target.normal_switch_callback", 136 | "主人房台灯":"target.normal_switch_callback", 137 | "电灯":"target.normal_switch_callback", 138 | "空调":"target.normal_ril_callback", 139 | "新宇":"target.normal_person_callback", 140 | "芳莹":"target.normal_person_callback", 141 | "Github趋势":"target.github_trending_callback" 142 | }, 143 | "stop":{ 144 | "算了":"stop.stop_callback", 145 | "取消":"stop.stop_callback" 146 | }, 147 | "finish":{ 148 | "好吗":"finish.finish_callback", 149 | "谢谢":"finish.finish_callback" 150 | }, 151 | "next":{ 152 | "接着":"next.next_callback", 153 | "然后":"next.next_callback", 154 | "的时候":"next.next_callback" 155 | }, 156 | "logical" : { 157 | "而且":"logical.and_callback", 158 | "并且":"logical.and_callback", 159 | "或者":"logical.or_callback" 160 | }, 161 | "compare": { 162 | "大于":"compare.greater_callback", 163 | "等于":"compare.equal_callback", 164 | "不等于":"compare.not_equal_callback", 165 | "小于":"compare.less_callback" 166 | } 167 | }, 168 | "sound":{ 169 | "com_btn1":"com_btn1.wav", 170 | "com_btn2":"com_btn2.wav", 171 | "com_finish":"com_finish.mp3", 172 | "com_knock":"com_knock.mp3", 173 | "com_warn":"com_warning.mp3", 174 | "com_stop":"com_stop.mp3", 175 | "com_begin":"com_begin.mp3", 176 | "com_bell":"com_bell.mp3", 177 | "com_bell2":"com_bell2.mp3", 178 | "com_shoot":"com_shoot.mp3", 179 | "com_trash":"com_trash.mp3" 180 | }, 181 | "switchs":{ 182 | "电饭煲":"192.168.1.160", 183 | "风扇":"192.168.1.161", 184 | "电灯":"192.168.1.162", 185 | "热水器":"192.168.1.163", 186 | "落地灯":"192.168.1.201", 187 | "阳台灯":"192.168.1.202", 188 | "主人房台灯":"192.168.1.203" 189 | }, 190 | "ril_address":"" 191 | , 192 | "tag":{ 193 | "place":{ 194 | "家里":"F4B85E03F44D" 195 | }, 196 | "member": { 197 | "芳莹":"E2C56DB5DFFB48D2B060D0F5A71096E0", 198 | "新宇":"FDA50693A4E24FB1AFCFC6EB07647825" 199 | } 200 | }, 201 | "ping":{ 202 | "device": { 203 | "芳莹":"192.168.1.154", 204 | "新宇":"192.168.1.100" 205 | } 206 | }, 207 | "qqfm": { 208 | "server":"localhost:8888" 209 | }, 210 | "sensor": { 211 | "房间": "192.168.1.133" 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /usr/res/com_begin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_begin.mp3 -------------------------------------------------------------------------------- /usr/res/com_bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_bell.mp3 -------------------------------------------------------------------------------- /usr/res/com_bell2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_bell2.mp3 -------------------------------------------------------------------------------- /usr/res/com_btn1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_btn1.wav -------------------------------------------------------------------------------- /usr/res/com_btn2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_btn2.wav -------------------------------------------------------------------------------- /usr/res/com_finish.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_finish.mp3 -------------------------------------------------------------------------------- /usr/res/com_knock.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_knock.mp3 -------------------------------------------------------------------------------- /usr/res/com_shoot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_shoot.mp3 -------------------------------------------------------------------------------- /usr/res/com_start.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_start.mp3 -------------------------------------------------------------------------------- /usr/res/com_stop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_stop.mp3 -------------------------------------------------------------------------------- /usr/res/com_trash.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_trash.mp3 -------------------------------------------------------------------------------- /usr/res/com_warning.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/usr/res/com_warning.mp3 -------------------------------------------------------------------------------- /usr/stopwords.txt: -------------------------------------------------------------------------------- 1 | 个 2 | ! 3 | @ 4 | $ 5 | % 6 | ^ 7 | & 8 | * 9 | ( 10 | ) 11 | , 12 | ? 13 | 、 14 | 。 15 | “ 16 | ” 17 | 《 18 | 》 19 | ! 20 | , 21 | : 22 | ; 23 | ? 24 | 啊 25 | 阿 26 | 哎 27 | 唉 28 | 吧 29 | 嘿 30 | 哼 31 | 哗 32 | 咳 33 | 哩 34 | 嘛 35 | 呢 36 | 哦 37 | 呕 38 | 呸 39 | 哇 40 | 喂 41 | 呜 42 | 嘻 43 | 吓 44 | 嘘 45 | 呀 46 | 哟 47 | 哉 48 | 咱 49 | 咋 50 | 吱 51 | 呃 52 | 呗 53 | 咚 54 | 咦 55 | 喏 56 | 啐 57 | 嗬 58 | 嗯 59 | 嗳 60 | -------------------------------------------------------------------------------- /util/Res.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import os 19 | import json 20 | from util.log import * 21 | 22 | class Res: 23 | settings = {} 24 | base_path = os.path.abspath(os.path.join(os.path.dirname(__file__),"../usr/")) 25 | 26 | @staticmethod 27 | def get(name): 28 | elem = Res.settings 29 | try: 30 | for x in name.strip("/").split("/"): 31 | elem = elem.get(x) 32 | except: 33 | ERROR("invaild get method params : " + name) 34 | pass 35 | return elem 36 | 37 | @staticmethod 38 | def get_res_path(elem): 39 | elem = os.path.join(Res.base_path + '/res/', Res.get(elem)) 40 | return elem 41 | 42 | @staticmethod 43 | def init(path, force=False): 44 | if force == False and len(Res.settings) != 0: 45 | return Res.settings 46 | path = os.path.join(Res.base_path, path) 47 | with open(path) as init_file: 48 | init_json = json.load(init_file) 49 | if not init_json: 50 | ERROR("error: invaild init.json.") 51 | return 52 | else: 53 | Res.settings = init_json 54 | return Res.settings 55 | -------------------------------------------------------------------------------- /util/Util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import re 19 | import datetime 20 | import urllib2 21 | import json 22 | import os 23 | import errno 24 | from HTMLParser import HTMLParser 25 | 26 | 27 | def mkdir_p(path): 28 | try: 29 | os.makedirs(path) 30 | except OSError as exc: # Python >2.5 31 | if exc.errno == errno.EEXIST and os.path.isdir(path): 32 | pass 33 | else: 34 | raise 35 | 36 | UTIL_CN_NUM = { 37 | u'零': 0, 38 | u'一': 1, 39 | u'二': 2, 40 | u'两': 2, 41 | u'三': 3, 42 | u'四': 4, 43 | u'五': 5, 44 | u'六': 6, 45 | u'七': 7, 46 | u'八': 8, 47 | u'九': 9, 48 | } 49 | UTIL_CN_UNIT = { 50 | u'十': 10, 51 | u'百': 100, 52 | u'千': 1000, 53 | u'万': 10000, 54 | } 55 | 56 | 57 | def cn2dig(src): 58 | if src == "": 59 | return None 60 | m = re.match("\d+", src) 61 | if m: 62 | return m.group(0) 63 | rsl = 0 64 | unit = 1 65 | for item in src[::-1]: 66 | if item in UTIL_CN_UNIT.keys(): 67 | unit = UTIL_CN_UNIT[item] 68 | elif item in UTIL_CN_NUM.keys(): 69 | num = UTIL_CN_NUM[item] 70 | rsl += num*unit 71 | else: 72 | return None 73 | if rsl < unit: 74 | rsl += unit 75 | return str(rsl) 76 | 77 | 78 | def parse_time(msg): 79 | m = re.match(ur"(([0-9零一二两三四五六七八九十百]+[点:\.])?([0-9零一二三四五六七八九十百]+分)?)", msg) 80 | if m.group(0): 81 | m1 = None 82 | m2 = None 83 | if m.group(2): 84 | m1 = m.group(2) 85 | if m.group(3): 86 | m2 = m.group(3) 87 | 88 | if m1 is None: 89 | m2 = cn2dig(m2[:-1]) 90 | return m2.zfill(2) 91 | elif m2 is None: 92 | m1 = cn2dig(m1[:-1]) 93 | return "%s:00" % (m1.zfill(2),) 94 | else: 95 | m1 = cn2dig(m1[:-1]) 96 | m2 = cn2dig(m2[:-1]) 97 | return "%s:%s" % (m1.zfill(2), m2.zfill(2)) 98 | else: 99 | return None 100 | 101 | 102 | def parse_datetime(msg): 103 | if msg is None or len(msg) == 0: 104 | return None 105 | m = re.match(ur"([0-9零一二两三四五六七八九十]+年)?([0-9一二两三四五六七八九十]+月)?([0-9一二两三四五六七八九十]+[号日])?([0-9一二两三四五六七八九十]*[明后大天]+)?([上下午晚早]+)?([0-9零一二两三四五六七八九十百]+[点钟:\.小时整正]+)?([0-9零一二三四五六七八九十百]+分钟?)?([0-9零一二三四五六七八九十百]+秒钟?)?", msg) 106 | if m.group(0) is not None and len(m.group(0).strip()) != 0: 107 | res = { 108 | "year": m.group(1), 109 | "month": m.group(2), 110 | "day": m.group(3), 111 | "hour": m.group(6) if m.group(6) is not None else '00', 112 | "minute": m.group(7) if m.group(7) is not None else '00', 113 | "second": m.group(8) if m.group(8) is not None else '00', 114 | # "microsecond": '00', 115 | } 116 | params = {} 117 | for name in res: 118 | remove_count = 1 119 | if res[name] is not None and len(res[name]) != 0: 120 | if res[name].endswith(u"小时") or \ 121 | res[name].endswith(u"点正") or \ 122 | res[name].endswith(u"点整") or \ 123 | res[name].endswith(u"点钟") or \ 124 | res[name].endswith(u"秒钟") or \ 125 | res[name].endswith(u"分钟"): 126 | remove_count = 2 127 | params[name] = int(cn2dig(res[name][:-remove_count])) 128 | target_date = datetime.datetime.today().replace(**params) 129 | next_day = m.group(4) 130 | if next_day is not None: 131 | day = target_date.date().day 132 | if next_day == u"明天": 133 | target_date = target_date.replace(day=day+1) 134 | elif next_day == u"后天": 135 | target_date = target_date.replace(day=day+2) 136 | elif next_day == u"大后天": 137 | target_date = target_date.replace(day=day+3) 138 | elif next_day.endswith(u"天后"): 139 | try: 140 | days_after = int(cn2dig(next_day[:-2])) 141 | target_date = target_date.replace(day=day+days_after) 142 | except Exception, ex: 143 | return None 144 | else: 145 | return None 146 | is_pm = m.group(5) 147 | if is_pm is not None: 148 | if is_pm == u'下午' or is_pm == u'晚上': 149 | hour = target_date.time().hour 150 | if hour < 12: 151 | target_date = target_date.replace(hour=hour+12) 152 | return target_date 153 | elif is_pm == u"上午" or is_pm == u"早上": 154 | return target_date 155 | else: 156 | return None 157 | return target_date 158 | else: 159 | return None 160 | 161 | 162 | # def gap_for_timestring(msg): 163 | # if msg is None or len(msg) == 0: 164 | # return None 165 | # t = 0 166 | # is_pm = False 167 | # if msg.startswith(u"上午") or msg.startswith(u"早上"): 168 | # msg = msg[2:] 169 | # elif msg.startswith(u"下午") or msg.startswith(u"晚上"): 170 | # is_pm = True 171 | # msg = msg[2:] 172 | # 173 | # time_string = parse_time(msg) 174 | # if time_string is None: 175 | # return None 176 | # t_list = time_string.split(":") 177 | # target_hour = int(t_list[0]) 178 | # if is_pm: 179 | # target_hour = target_hour + 12 180 | # target_min = int(t_list[1]) 181 | # now = datetime.datetime.now() 182 | # cur_hour = now.hour 183 | # cur_min = now.minute 184 | # if cur_hour < target_hour or \ 185 | # (cur_hour <= target_hour and cur_min <= target_min): 186 | # t = t + (target_hour - cur_hour)*60*60 + (target_min - cur_min)*60 187 | # else: 188 | # t = t + 24*60*60 - \ 189 | # ((cur_hour - target_hour)*60*60 + (cur_min - target_min)*60) 190 | # return t 191 | 192 | 193 | def gap_for_timestring(msg): 194 | if msg is None or len(msg) == 0: 195 | return None 196 | target = parse_datetime(msg) 197 | if target is None: 198 | return None 199 | now = datetime.datetime.now() 200 | if now > target: 201 | target = target + datetime.timedelta(days=1) 202 | delta = target - now 203 | return delta.total_seconds() 204 | 205 | 206 | def wait_for_period(period): 207 | t1 = period[0] 208 | t2 = period[1] 209 | 210 | tt1 = gap_for_timestring(t1) 211 | tt2 = gap_for_timestring(t2) 212 | 213 | if tt1 >= tt2: #draw some graphs to understand this 214 | return 0 215 | else: 216 | return tt1 217 | 218 | 219 | def var_parse_value(var_value): 220 | if empty_str(var_value): 221 | return None 222 | if var_value.startswith(u'逻辑'): 223 | if var_value.endswith(u'真'): 224 | return True 225 | elif var_value.endswith(u'假'): 226 | return False 227 | else: 228 | return None 229 | elif var_value.startswith(u'数值'): 230 | try: 231 | value = int(var_value[2:]) 232 | except: 233 | return None 234 | return value 235 | elif var_value.startswith(u'字符'): 236 | try: 237 | value = unicode(var_value[2:]) 238 | except: 239 | return None 240 | return value 241 | else: 242 | return None 243 | 244 | 245 | def xunicode(u): 246 | if u is None: 247 | return u'' 248 | else: 249 | return u 250 | 251 | def what_day_is_today(): 252 | return datetime.datetime.today().weekday() 253 | 254 | g_workday_cache = None 255 | g_workday_fetched = None 256 | def is_workday_today(): 257 | global g_workday_cache, g_workday_fetched 258 | 259 | check_date =datetime.datetime.today() 260 | if g_workday_fetched is None or g_workday_fetched.day < check_date.day: 261 | print "init workday" 262 | weekday_api = "http://lehome.sinaapp.com/tool/workdaychecker?d=" + check_date.strftime('%Y%m%d') 263 | try: 264 | day_type = urllib2.urlopen(weekday_api).read() 265 | g_workday_cache = int(day_type) 266 | g_workday_fetched = check_date 267 | return g_workday_cache 268 | except Exception, ex: 269 | print ex 270 | elif not g_workday_cache is None: 271 | return g_workday_cache 272 | 273 | if what_day_is_today() > 4: 274 | return 1 275 | else: 276 | return 0 277 | 278 | class MLStripper(HTMLParser): 279 | def __init__(self): 280 | self.reset() 281 | self.fed = [] 282 | 283 | def handle_data(self, d): 284 | self.fed.append(d) 285 | 286 | def get_data(self): 287 | return ''.join(self.fed) 288 | 289 | 290 | def strip_tags(html): 291 | s = MLStripper() 292 | s.feed(html) 293 | return s.get_data() 294 | 295 | 296 | def empty_str(src): 297 | if src is None or len(src) == 0: 298 | return True 299 | return False 300 | 301 | if __name__ == "__main__": 302 | # print parse_time(u"7:30") 303 | # print parse_time(u"两点30分") 304 | # print parse_time(u"7点") 305 | # print parse_time(u"五分") 306 | # print parse_time(u"一百零五分") 307 | # print parse_time(u"七点五分") 308 | # print parse_time(u"七点零五分") 309 | # print parse_time(u"9点04分") 310 | 311 | 312 | # print parse_datetime(u"7点") 313 | # print parse_datetime(u"五分") 314 | # print parse_datetime(u"七点五分") 315 | # print parse_datetime(u"七点零五分") 316 | # print parse_datetime(u"9点04分") 317 | # print parse_datetime(u"下午9点04分") 318 | # print parse_datetime(u"6月三十日04分") 319 | # print parse_datetime(u"1995年6月10号下午3点41分50秒") 320 | # print parse_datetime(u"明天下午3点41分50秒") 321 | # print parse_datetime(u"5天后下午3点41分50秒") 322 | # print parse_datetime(u"5天后下午3点整") 323 | # # 324 | # print gap_for_timestring(u"3月6日下午四点") 325 | # print parse_datetime(u"7点钟") 326 | # print gap_for_timestring(u"2秒") 327 | # print gap_for_timestring(u"5分钟") 328 | print gap_for_timestring(u"早上8点钟") 329 | for i in [1]*10: 330 | print type(is_workday_today()) 331 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/util/__init__.py -------------------------------------------------------------------------------- /util/log.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Xinyu, He 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import inspect 17 | import traceback 18 | import logging 19 | import logging.handlers 20 | 21 | import config 22 | from util.Util import mkdir_p 23 | 24 | mkdir_p("log") 25 | 26 | tmpfd_path = config.TMPFS_PATH + "log" 27 | mkdir_p(tmpfd_path) 28 | 29 | if os.path.isdir(tmpfd_path): 30 | file_name = os.path.join(tmpfd_path, 'home_debug.log') 31 | else: 32 | file_name = 'log/debug_home.log' 33 | 34 | debug_logger = logging.getLogger('DebugLog') 35 | handler = logging.handlers.RotatingFileHandler(file_name, maxBytes=50*1024*1024) 36 | formatter = logging.Formatter("%(asctime)s - [%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s") 37 | handler.setFormatter(formatter) 38 | debug_logger.setLevel(logging.DEBUG) 39 | debug_logger.addHandler(handler) 40 | debug_logger.propagate = False # now if you use logger it will not log to console. 41 | 42 | if config.DEBUG_ENABLE is False: 43 | debug_logger.setLevel(logging.CRITICAL) 44 | 45 | comm_name = 'log/home.log' 46 | comm_logger = logging.getLogger('CommonLog') 47 | handler = logging.handlers.RotatingFileHandler(comm_name, maxBytes=20*1024*1024) 48 | formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s [%(filename)s - %(funcName)s] ') 49 | handler.setFormatter(formatter) 50 | comm_logger.setLevel(logging.INFO) 51 | comm_logger.addHandler(handler) 52 | # comm_logger.propagate = False # now if you use logger it will not log to console. 53 | 54 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s') 55 | 56 | # def stack_info_debug(info): 57 | # stack_info = inspect.currentframe().f_back.f_code.co_name 58 | # debug_logger.debug("%s: %s" % (stack_info, info)) 59 | 60 | def TRACE_EX(): 61 | comm_logger.error(traceback.format_exc()) 62 | 63 | DEBUG = debug_logger.debug 64 | # DEBUG = stack_info_debug # only output to file 65 | INFO = comm_logger.info 66 | WARN = comm_logger.warning 67 | ERROR = comm_logger.error 68 | CRITICAL = comm_logger.critical 69 | 70 | FDEBUG = debug_logger.debug 71 | FINFO = debug_logger.info 72 | FWARN = debug_logger.warning 73 | FERROR = debug_logger.error 74 | FCRITICAL = debug_logger.critical 75 | 76 | EXCEPTION = comm_logger.exception 77 | 78 | if os.path.isdir(tmpfd_path): 79 | INFO("use tmpfs as debug file") 80 | else: 81 | WARN("tmpfs log file is disable") 82 | -------------------------------------------------------------------------------- /util/thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import threading 19 | 20 | 21 | class StoppableThread(threading.Thread): 22 | """Thread class with a stop() method. The thread itself has to check 23 | regularly for the stopped() condition.""" 24 | 25 | def __init__(self, target, args=None): 26 | super(StoppableThread, self).__init__(target=target, args=args) 27 | self._stop = threading.Event() 28 | self.suspend_event = None 29 | self.thread_idx = -1 30 | 31 | def waitUtil(self, sec): 32 | self._stop.wait(sec) 33 | 34 | def stop(self): 35 | if not self.suspend_event is None: 36 | self.suspend_event.set() 37 | self._stop.set() 38 | 39 | def stopped(self): 40 | return self._stop.isSet() 41 | 42 | 43 | class TimerThread(threading.Thread): 44 | """Thread class with a stop() method. The thread itself has to check 45 | regularly for the stopped() condition.""" 46 | 47 | def __init__(self, interval, target, args={}): 48 | super(TimerThread, self).__init__() 49 | self.interval = interval 50 | self.target = target 51 | self.args = args 52 | self._stop = threading.Event() 53 | self.setDaemon(True) # don't forget to set daemon 54 | 55 | def run(self): 56 | while not self._stop.wait(self.interval): 57 | self.target(**self.args) 58 | 59 | def stop(self): 60 | self._stop.set() 61 | 62 | def set_stopped(self): 63 | return self._stop.isSet() 64 | -------------------------------------------------------------------------------- /vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/vendor/__init__.py -------------------------------------------------------------------------------- /vendor/baidu_push/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/vendor/baidu_push/__init__.py -------------------------------------------------------------------------------- /vendor/baidu_push/lib/ChannelException.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | class ChannelException(Exception): 4 | def __init__(self, error_msg, error_code): 5 | self.error_msg = error_msg 6 | self.error_code = error_code 7 | -------------------------------------------------------------------------------- /vendor/baidu_push/lib/RequestCore.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # _*_ coding: UTF-8 _*_ 3 | 4 | ### 5 | # 本文件百度云服务PHP版本SDK的公共网络交互功能 6 | # 7 | # @author 百度移动.云事业部 8 | # @copyright Copyright (c) 2012-2020 百度在线网络技术(北京)有限公司 9 | # @version 1.0.0 10 | # @package 11 | ## 12 | 13 | 14 | import urlparse 15 | 16 | import pycurl 17 | 18 | import StringIO 19 | 20 | 21 | class RequestCore(object): 22 | """封装curl,提供网络交互功能,组网络请求包,并保存返回结果""" 23 | #类常量 24 | HTTP_GET = 'GET' 25 | 26 | HTTP_POST = 'POST' 27 | 28 | HTTP_PUT = 'PUT' 29 | 30 | HTTP_DELETE = 'DELETE' 31 | 32 | HTTP_HEAD = 'HEAD' 33 | 34 | 35 | def __init__(self, url = None, proxy = None, helpers = None): 36 | self.request_url = url 37 | self.method = RequestCore.HTTP_POST 38 | self.request_headers = dict() 39 | self.request_body = None 40 | self.response = None 41 | self.response_headers = None 42 | self.response_body = None 43 | self.response_code = None 44 | self.response_info = None 45 | self.curl_handle = None 46 | self.proxy = None 47 | self.username = None 48 | self.password = None 49 | self.curlopts = None 50 | self.debug_mode = False 51 | self.request_class = 'RequestCore' 52 | self.response_class = 'ResponseCore' 53 | self.useragent = 'RequestCore/1.4.2' 54 | if(isinstance(helpers, dict)): 55 | if(helpers.has_key() and helpers['request'] is not None): 56 | self.request_class = helpers['request'] 57 | if(helpers.has_key() and helpers['response'] is not None): 58 | self.response_class = helpers['response'] 59 | if(proxy is not None): 60 | self.set_proxy(proxy) 61 | 62 | def set_credentials(self, username, password): 63 | self.username = username 64 | self.password = password 65 | 66 | def add_header(self, key, value): 67 | self.request_headers[key] = value 68 | 69 | def remove_header(self, key): 70 | if(self.request_headers.has_key(key)): 71 | del self.request_headers[key] 72 | 73 | def set_method(self, method): 74 | self.method = method.upper() 75 | 76 | def set_useragent(self, ua): 77 | self.useragent = ua 78 | 79 | def set_body(self, body): 80 | self.request_body = body 81 | 82 | def set_request_url(self, url): 83 | self.request_url = url 84 | 85 | def set_curlopts(self, curlopts): 86 | self.curlopts = curlopts 87 | 88 | def set_proxy(self, proxy): 89 | self.proxy = urlparse.urlparse(proxy) 90 | 91 | def handle_request(self): 92 | curl_handle = pycurl.Curl() 93 | # set default options. 94 | curl_handle.setopt(pycurl.URL, self.request_url) 95 | curl_handle.setopt(pycurl.REFERER, self.request_url) 96 | curl_handle.setopt(pycurl.USERAGENT, self.useragent) 97 | curl_handle.setopt(pycurl.TIMEOUT, 5184000) 98 | curl_handle.setopt(pycurl.CONNECTTIMEOUT, 120) 99 | curl_handle.setopt(pycurl.HEADER, True) 100 | # curl_handle.setopt(pycurl.VERBOSE, 1) 101 | curl_handle.setopt(pycurl.FOLLOWLOCATION, 1) 102 | curl_handle.setopt(pycurl.MAXREDIRS, 5) 103 | if(self.request_headers and len(self.request_headers) > 0): 104 | tmplist = list() 105 | for(key, value) in self.request_headers.items(): 106 | tmplist.append(key + ':' + value) 107 | curl_handle.setopt(pycurl.HTTPHEADER, tmplist) 108 | #目前只需支持POST 109 | curl_handle.setopt(pycurl.HTTPPROXYTUNNEL, 1) 110 | curl_handle.setopt(pycurl.POSTFIELDS, self.request_body) 111 | 112 | response = StringIO.StringIO() 113 | curl_handle.setopt(pycurl.WRITEFUNCTION, response.write) 114 | curl_handle.perform() 115 | 116 | self.response_code = curl_handle.getinfo(curl_handle.HTTP_CODE) 117 | header_size = curl_handle.getinfo(curl_handle.HEADER_SIZE) 118 | resp_str = response.getvalue() 119 | self.response_headers = resp_str[0 : header_size] 120 | self.response_body = resp_str[header_size : ] 121 | 122 | response.close() 123 | curl_handle.close() 124 | 125 | 126 | def get_response_header(self, header = None): 127 | if(header is not None): 128 | return self.response_headers[header] 129 | return self.response_headers 130 | 131 | def get_response_body(self): 132 | return self.response_body 133 | 134 | def get_response_code(self): 135 | return self.response_code 136 | 137 | 138 | # 139 | # Container for all response-related methods 140 | # 141 | 142 | class ResponseCore(object): 143 | 144 | def __init__(self, header, body, status = None): 145 | self.header = header 146 | self.body = body 147 | self.status = status 148 | 149 | def isOK(self, codes = None): 150 | if(codes == None): 151 | codes = [200, 201, 204, 206] 152 | return self.status in codes 153 | else: 154 | return self == codes 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /vendor/baidu_push/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | lib 3 | """ 4 | -------------------------------------------------------------------------------- /vendor/gpio/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # io test code for pcDuino ( http://www.pcduino.com ) 4 | # 5 | __all__ = ['HIGH', 'LOW', 'INPUT', 'OUTPUT','digitalWrite', 'digitalRead', "pinMode"] 6 | 7 | _GPIO_PINS = ('gpio0','gpio1','gpio2','gpio3','gpio4','gpio5','gpio6','gpio7', 8 | 'gpio8', 'gpio9', 'gpio10', 'gpio11', 'gpio12', 'gpio13', 9 | 'gpio14', 'gpio15', 'gpio16', 'gpio17', 'gpio18', 'gpio19') 10 | 11 | _PIN_FD_PATH = '/sys/devices/virtual/misc/gpio/pin/%s' 12 | _MODE_FD_PATH = '/sys/devices/virtual/misc/gpio/mode/%s' 13 | HIGH = 1 14 | LOW = 0 15 | INPUT = 0 16 | OUTPUT = 1 17 | 18 | class InvalidChannelException(Exception): 19 | """The channel sent is invalid on pcDuino board """ 20 | pass 21 | 22 | def _GetValidId(channel): 23 | if channel in _GPIO_PINS: 24 | return channel 25 | else: 26 | raise InvalidChannelException 27 | 28 | def digitalWrite(channel, value): 29 | """Write to a GPIO channel""" 30 | id = _GetValidId(channel) 31 | with open(_PIN_FD_PATH % id, 'w') as f: 32 | f.write('1' if value == HIGH else '0') 33 | 34 | def digitalRead(channel): 35 | """Read from a GPIO channel""" 36 | id = _GetValidId(channel) 37 | with open(_PIN_FD_PATH % id, 'r') as f: 38 | return f.read(1) == '1' 39 | 40 | def pinMode(channel, mode): 41 | """ Set Mode of a GPIO channel """ 42 | id = _GetValidId(channel) 43 | with open(_MODE_FD_PATH % id, 'w') as f: 44 | f.write('0' if mode == INPUT else '1') 45 | -------------------------------------------------------------------------------- /vendor/ibeacon_scan: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # iBeacon Scan by Radius Networks 3 | 4 | if [[ $1 == "parse" ]]; then 5 | packet="" 6 | capturing="" 7 | count=0 8 | while read line 9 | do 10 | count=$[count + 1] 11 | if [ "$capturing" ]; then 12 | if [[ $line =~ ^[0-9a-fA-F]{2}\ [0-9a-fA-F] ]]; then 13 | packet="$packet $line" 14 | else 15 | if [[ $packet =~ ^04\ 3E\ 2A\ 02\ 01\ .{26}\ 02\ 01\ .{14}\ 02\ 15 ]]; then 16 | UUID=`echo $packet | sed 's/^.\{69\}\(.\{47\}\).*$/\1/'` 17 | MAJOR=`echo $packet | sed 's/^.\{117\}\(.\{5\}\).*$/\1/'` 18 | MINOR=`echo $packet | sed 's/^.\{123\}\(.\{5\}\).*$/\1/'` 19 | POWER=`echo $packet | sed 's/^.\{129\}\(.\{2\}\).*$/\1/'` 20 | UUID=`echo $UUID | sed -e 's/\ //g' -e 's/^\(.\{8\}\)\(.\{4\}\)\(.\{4\}\)\(.\{4\}\)\(.\{12\}\)$/\1-\2-\3-\4-\5/'` 21 | MAJOR=`echo $MAJOR | sed 's/\ //g'` 22 | MAJOR=`echo "ibase=16; $MAJOR" | bc` 23 | MINOR=`echo $MINOR | sed 's/\ //g'` 24 | MINOR=`echo "ibase=16; $MINOR" | bc` 25 | POWER=`echo "ibase=16; $POWER" | bc` 26 | POWER=$[POWER - 256] 27 | RSSI=`echo $packet | sed 's/^.\{132\}\(.\{2\}\).*$/\1/'` 28 | RSSI=`echo "ibase=16; $RSSI" | bc` 29 | RSSI=$[RSSI - 256] 30 | if [[ $2 == "-b" ]]; then 31 | echo "$UUID $MAJOR $MINOR $POWER $RSSI" 32 | else 33 | echo "UUID: $UUID MAJOR: $MAJOR MINOR: $MINOR POWER: $POWER RSSI: $RSSI" 34 | fi 35 | fi 36 | capturing="" 37 | packet="" 38 | fi 39 | fi 40 | 41 | if [ ! "$capturing" ]; then 42 | if [[ $line =~ ^\> ]]; then 43 | packet=`echo $line | sed 's/^>.\(.*$\)/\1/'` 44 | capturing=1 45 | fi 46 | fi 47 | done 48 | else 49 | sudo hcitool -i hci0 lescan --duplicates 1>/dev/null & 50 | if [ "$(pidof hcitool)" ]; then 51 | sudo hcidump --raw | ./$0 parse $1 52 | fi 53 | fi 54 | -------------------------------------------------------------------------------- /vendor/mipush/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/vendor/mipush/__init__.py -------------------------------------------------------------------------------- /vendor/mipush/mipush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | # Copyright 2014 Xinyu, He 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | import json 19 | import httplib 20 | import urllib 21 | import time 22 | 23 | ERR_OK = 0 24 | ERR_PARAM = -1 25 | ERR_HTTP = -100 26 | ERR_RETURN_DATA = -101 27 | 28 | 29 | class MIPushHelper(object): 30 | MIPUSH_HOST = 'api.xmpush.xiaomi.com' 31 | MIPUSH_PORT = 443 32 | MIPUSH_SECRET_KEY = '' 33 | TIMEOUT = 10 34 | HTTP_METHOD = 'POST' 35 | HTTP_HEADERS = {'Authorization': MIPUSH_SECRET_KEY, 'Content-Type': 'application/x-www-form-urlencoded'} 36 | 37 | STR_RET_CODE = 'code' 38 | STR_ERR_MSG = 'reason' 39 | STR_RESULT = 'data' 40 | 41 | @classmethod 42 | def SetServer(cls, host=MIPUSH_HOST, port=MIPUSH_PORT): 43 | cls.MIPUSH_HOST = host 44 | cls.MIPUSH_PORT = port 45 | 46 | @classmethod 47 | def SetAuthorization(cls, secretKey): 48 | cls.HTTP_HEADERS['Authorization'] = secretKey 49 | 50 | @classmethod 51 | def GenTimestamp(cls): 52 | return int(time.time()) 53 | 54 | @classmethod 55 | def Request(cls, path, params): 56 | 57 | if 'Authorization' not in cls.HTTP_HEADERS or len(cls.HTTP_HEADERS['Authorization']) == 0: 58 | return ERR_PARAM, '', None 59 | 60 | httpClient = httplib.HTTPSConnection(cls.MIPUSH_HOST, cls.MIPUSH_PORT, timeout=cls.TIMEOUT) 61 | if cls.HTTP_METHOD == 'GET': 62 | httpClient.request(cls.HTTP_METHOD, ('%s?%s' % (path, urllib.urlencode(params))), headers=cls.HTTP_HEADERS) 63 | elif cls.HTTP_METHOD == 'POST': 64 | httpClient.request(cls.HTTP_METHOD, path, urllib.urlencode(params), headers=cls.HTTP_HEADERS) 65 | else: 66 | # invalid method 67 | return ERR_PARAM, '', None 68 | 69 | response = httpClient.getresponse() 70 | ret_code = ERR_RETURN_DATA 71 | err_msg = '' 72 | result = {} 73 | if 200 != response.status: 74 | ret_code = ERR_HTTP 75 | else: 76 | data = response.read() 77 | ret_dict = json.loads(data) 78 | if cls.STR_RET_CODE in ret_dict: 79 | ret_code = ret_dict[cls.STR_RET_CODE] 80 | if cls.STR_ERR_MSG in ret_dict: 81 | err_msg = ret_dict[cls.STR_ERR_MSG] 82 | if cls.STR_RESULT in ret_dict: 83 | result = ret_dict[cls.STR_RESULT] 84 | return ret_code, err_msg, result 85 | 86 | 87 | class MIPush(object): 88 | PATH_PUSH_TAGS = '/v2/message/topic' 89 | 90 | def __init__(self, app_secret): 91 | self._secret = 'key=' + str(app_secret) 92 | MIPushHelper.SetAuthorization(self._secret) 93 | 94 | def push_topic_passthrough(self, payload, package_name, topic, 95 | noti_type=-1, time_to_live=1000 * 3600): 96 | params = { 97 | 'pass_through': 1, 98 | 'payload': payload, 99 | 'restricted_package_name': package_name, 100 | 'topic': topic, 101 | 'notify_type': noti_type, 102 | 'time_to_live': int(time_to_live) 103 | } 104 | ret_code, err_msg, result = self.request(MIPush.PATH_PUSH_TAGS, params) 105 | if ret_code != 0: 106 | print ret_code, err_msg, result 107 | else: 108 | print ret_code, err_msg, result 109 | return ret_code 110 | 111 | def request(self, path, params): 112 | return MIPushHelper.Request(path, params) 113 | 114 | 115 | def main(): 116 | mipush = MIPush('3b6uuQ2wE0ox80Tv4kV2fw==') 117 | mipush.push_topic_passthrough('test', 'my.home.lehome', '12345678') 118 | 119 | 120 | if __name__ == "__main__": 121 | main() 122 | -------------------------------------------------------------------------------- /vendor/xg_push/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/vendor/xg_push/__init__.py -------------------------------------------------------------------------------- /vendor/xg_push/demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | ''' 4 | 5 | Copyright © 1998 - 2013 Tencent. All Rights Reserved. 腾讯公司 版权所有 6 | 7 | ''' 8 | 9 | import xinge 10 | import json 11 | 12 | # 定义通知 13 | def BuildNotification(): 14 | msg = xinge.Message() 15 | msg.type = xinge.Message.TYPE_NOTIFICATION 16 | msg.title = 'some title' 17 | msg.content = 'some content' 18 | # 消息为离线设备保存的时间,单位为秒。默认为0,表示只推在线设备 19 | msg.expireTime = 86400 20 | # 定时推送,非必须 21 | #msg.sendTime = '2012-12-12 18:48:00' 22 | # 自定义键值对,key和value都必须是字符串,非必须 23 | msg.custom = {'aaa':'111', 'bbb':'222'} 24 | # 使用多包名推送模式,详细说明参见文档和wiki,如果您不清楚该字段含义,则无需设置 25 | #msg.multiPkg = 1 26 | 27 | # 允许推送时段设置,非必须 28 | #ti1 = xinge.TimeInterval(9, 30, 11, 30) 29 | #ti2 = xinge.TimeInterval(14, 0, 17, 0) 30 | #msg.acceptTime = (ti1, ti2) 31 | 32 | # 通知展示样式,仅对通知有效 33 | # 样式编号为2,响铃,震动,不可从通知栏清除,不影响先前通知 34 | style = xinge.Style(2, 1, 1, 0, 0) 35 | msg.style = style 36 | 37 | # 点击动作设置,仅对通知有效 38 | # 以下例子为点击打开url 39 | action = xinge.ClickAction() 40 | action.actionType = xinge.ClickAction.TYPE_URL 41 | action.url = 'http://xg.qq.com' 42 | # 打开url不需要用户确认 43 | action.confirmOnUrl = 0 44 | msg.action = action 45 | 46 | # 以下例子为点击打开intent。例子中的intent将打开拨号界面并键入10086 47 | # 使用intent.toUri(Intent.URI_INTENT_SCHEME)方法来得到序列化后的intent字符串,自定义intent参数也包含在其中 48 | #action = xinge.ClickAction() 49 | #action.actionType = xinge.ClickAction.TYPE_INTENT 50 | #action.intent = 'intent:10086#Intent;scheme=tel;action=android.intent.action.DIAL;S.key=value;end' 51 | #msg.action = action 52 | 53 | return msg 54 | 55 | # 定义透传消息 56 | def BuildMsg(): 57 | msg = xinge.Message() 58 | msg.type = xinge.Message.TYPE_MESSAGE 59 | msg.title = 'some title' 60 | msg.content = 'some content' 61 | # 消息为离线设备保存的时间,单位为秒。默认为0,表示只推在线设备 62 | msg.expireTime = 86400 63 | # 定时推送,若不需定时可以不设置 64 | #msg.sendTime = '2012-12-12 18:48:00' 65 | # 自定义键值对,key和value都必须是字符串 66 | msg.custom = {'aaa':'111', 'bbb':'222'} 67 | # 使用多包名推送模式,详细说明参见文档和wiki,如果您不清楚该字段含义,则无需设置 68 | #msg.multiPkg = 1 69 | 70 | # 允许推送时段设置,非必须 71 | #ti1 = xinge.TimeInterval(9, 30, 11, 30) 72 | #ti2 = xinge.TimeInterval(14, 0, 17, 0) 73 | #msg.acceptTime = (ti1, ti2) 74 | 75 | return msg 76 | 77 | # 定义iOS消息 78 | def BuildIOSMsg(): 79 | msg = xinge.MessageIOS() 80 | # alert字段可以是字符串或json对象,参见APNS文档 81 | msg.alert = "alert content" 82 | # 消息为离线设备保存的时间,单位为秒。默认为0,表示只推在线设备 83 | msg.expireTime = 3600 84 | # 定时推送,若不需定时可以不设置 85 | #msg.sendTime = '2012-12-12 18:48:00' 86 | # 自定义键值对,value可以是json允许的类型 87 | msg.custom = {'aaa':'111', 'bbb':{'b1':1, 'b2':2}} 88 | 89 | # 允许推送时段设置,非必须 90 | #ti1 = xinge.TimeInterval(9, 30, 11, 30) 91 | #ti2 = xinge.TimeInterval(14, 0, 17, 0) 92 | #msg.acceptTime = (ti1, ti2) 93 | 94 | return msg 95 | 96 | # 按token推送 97 | def DemoPushToken(x, msg): 98 | # 第三个参数environment仅在iOS下有效。ENV_DEV表示推送APNS开发环境 99 | ret = x.PushSingleDevice('some_token', msg, xinge.XingeApp.ENV_DEV) 100 | print ret 101 | 102 | # 按账号推送 103 | def DemoPushAccount(x, msg): 104 | ret = x.PushSingleAccount(0, '123456', msg, xinge.XingeApp.ENV_DEV) 105 | print ret 106 | 107 | # 按多账号推送 108 | def DemoPushAccountList(x, msg): 109 | accountList = list() 110 | accountList.append('241008') 111 | ret = x.PushAccountList(0, accountList, msg, xinge.XingeApp.ENV_DEV) 112 | print ret 113 | 114 | # 按app推送 115 | def DemoPushAll(x, msg): 116 | # 第三个参数environment仅在iOS下有效。ENV_DEV表示推送APNS开发环境 117 | ret = x.PushAllDevices(0, msg, xinge.XingeApp.ENV_DEV) 118 | print ret 119 | 120 | # 按tag推送 121 | def DemoPushTags(x, msg): 122 | # 第三个参数environment仅在iOS下有效。ENV_DEV表示推送开发环境 123 | ret = x.PushTags(0, ('tag1','tag2'), 'AND', msg, xinge.XingeApp.ENV_DEV) 124 | print ret 125 | 126 | # 查询群发任务状态 127 | def DemoQueryPushStatus(x): 128 | # 查询群发id为31和30的消息状态 129 | ret = x.QueryPushStatus(('31','30')) 130 | print ret 131 | 132 | # 查询app覆盖设备数量 133 | def DemoQueryDeviceNum(x): 134 | ret = x.QueryDeviceCount() 135 | print ret 136 | 137 | # 查询tag 138 | def DemoQueryTags(x): 139 | # 查询头5个tag 140 | ret = x.QueryTags(0, 5) 141 | print ret 142 | 143 | # 取消尚未触发的定时群发任务 144 | def DemoCancelTimingPush(x): 145 | ret = x.CancelTimingPush('31') 146 | print ret 147 | 148 | # token-标签绑定 149 | def DemoBatchSetTag(x): 150 | # 切记把这里的示例tag和示例token修改为你的真实tag和真实token 151 | pairs = [] 152 | pairs.append(xinge.TagTokenPair("tag1","token00000000000000000000000000000000001")) 153 | pairs.append(xinge.TagTokenPair("tag2","token00000000000000000000000000000000002")) 154 | ret = x.BatchSetTag(pairs) 155 | print ret 156 | 157 | # token-标签解绑 158 | def DemoBatchDelTag(x): 159 | # 切记把这里的示例tag和示例token修改为你的真实tag和真实token 160 | pairs = [] 161 | pairs.append(xinge.TagTokenPair("tag1","token00000000000000000000000000000000001")) 162 | pairs.append(xinge.TagTokenPair("tag2","token00000000000000000000000000000000002")) 163 | ret = x.BatchDelTag(pairs) 164 | print ret 165 | 166 | # 查询token绑定的标签 167 | def DemoQueryTokenTags(x): 168 | # 请把这里示例token修改为你的真实token 169 | ret = x.QueryTokenTags('token00000000000000000000000000000000001') 170 | print ret 171 | 172 | # 查询标签绑定的设备数 173 | def DemoQueryTagTokenNum(x): 174 | # 请把这里示例tag修改为你的真实tag 175 | ret = x.QueryTagTokenNum('tag1') 176 | print ret 177 | 178 | if '__main__' == __name__: 179 | # 初始化app对象,设置有效的access id和secret key 180 | x = xinge.XingeApp(0, 'secret') 181 | 182 | # 构建一条消息,可以是通知或者透传消息 183 | #msg = BuildMsg() 184 | msg = BuildNotification() 185 | 186 | # 构建iOS消息。注意iOS只有一种消息类型,不分通知/透传 187 | # iOS推送,调用push接口时切记设置ENV 188 | #msg = BuildIOSMsg() 189 | 190 | DemoPushToken(x, msg) 191 | DemoPushAccount(x, msg) 192 | DemoPushAccountList(x, msg) 193 | DemoPushAll(x, msg) 194 | DemoPushTags(x, msg) 195 | DemoQueryPushStatus(x) 196 | DemoQueryDeviceNum(x) 197 | DemoQueryTags(x) 198 | DemoCancelTimingPush(x) 199 | DemoBatchSetTag(x) 200 | DemoBatchDelTag(x) 201 | DemoQueryTokenTags(x) 202 | DemoQueryTagTokenNum(x) 203 | 204 | 205 | -------------------------------------------------------------------------------- /vendor/yunba/stdinpub_present: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legendmohe/LEHome/a959a2fe64a23c58de7c0ff3254eae8c27732320/vendor/yunba/stdinpub_present --------------------------------------------------------------------------------