|
| 1 | +Push Server |
| 2 | +=========== |
| 3 | + |
| 4 | +The package provides a push server to act on events from devices, |
| 5 | +such as those from Zigbee devices connected to a gateway device. |
| 6 | +The server itself acts as a miio device receiving the events it has :ref:`subscribed to receive<events_subscribe>`, |
| 7 | +and calling the registered callbacks accordingly. |
| 8 | + |
| 9 | +.. note:: |
| 10 | + |
| 11 | + While the eventing has been so far tested only on gateway devices, other devices that allow scene definitions on the |
| 12 | + mobile app may potentially support this functionality. See :ref:`how to obtain event information<events_obtain>` for details |
| 13 | + how to check if your target device supports this functionality. |
| 14 | + |
| 15 | + |
| 16 | +1. The push server is started and listens for incoming messages (:meth:`PushServer.start`) |
| 17 | +2. A miio device and its callback needs to be registered to the push server (:meth:`PushServer.register_miio_device`). |
| 18 | +3. A message is sent to the miio device to subscribe a specific event to the push server, |
| 19 | + basically a local scene is made with as target the push server (:meth:`PushServer.subscribe_event`). |
| 20 | +4. The device will start keep alive communication with the push server (pings). |
| 21 | +5. When the device triggers an event (e.g., a button is pressed), |
| 22 | + the push server gets notified by the device and executes the registered callback. |
| 23 | + |
| 24 | + |
| 25 | +Events |
| 26 | +------ |
| 27 | + |
| 28 | +Events are the triggers for a scene in the mobile app. |
| 29 | +Most triggers that can be used in the mobile app can be converted to a event that can be registered to the push server. |
| 30 | +For example: pressing a button, opening a door-sensor, motion being detected, vibrating a sensor or flipping a cube. |
| 31 | +When such a event happens, |
| 32 | +the miio device will immediately send a message to to push server, |
| 33 | +which will identify the sender and execute its callback function. |
| 34 | +The callback function can be used to act on the event, |
| 35 | +for instance when motion is detected turn on the light. |
| 36 | + |
| 37 | +Callbacks |
| 38 | +--------- |
| 39 | + |
| 40 | +Gateway-like devices will have a single callback for all connected Zigbee devices. |
| 41 | +The `source_device` argument is set to the device that caused the event e.g. "lumi.123456789abcdef". |
| 42 | + |
| 43 | +Multiple events of the same device can be subscribed to, for instance both opening and closing a door-sensor. |
| 44 | +The `action` argument is set to the action e.g., "open" or "close" , |
| 45 | +that was defined in the :class:`PushServer.EventInfo` used for subscribing to the event. |
| 46 | + |
| 47 | +Lastly, the `params` argument provides additional information about the event, if available. |
| 48 | + |
| 49 | +Therefore, the callback functions need to have the following signature: |
| 50 | + |
| 51 | +.. code-block:: |
| 52 | +
|
| 53 | + def callback(source_device, action, params): |
| 54 | +
|
| 55 | +
|
| 56 | +.. _events_subscribe: |
| 57 | + |
| 58 | +Subscribing to Events |
| 59 | +~~~~~~~~~~~~~~~~~~~~~ |
| 60 | +In order to subscribe to a event a few steps need to be taken, |
| 61 | +we assume that a device class has already been initialized to which the events belong: |
| 62 | + |
| 63 | +1. Create a push server instance: |
| 64 | + |
| 65 | +:: |
| 66 | + |
| 67 | + server = PushServer(miio_device.ip) |
| 68 | + |
| 69 | +.. note:: |
| 70 | + |
| 71 | + The server needs an IP address of a real, working miio device as it connects to it to find the IP address to bind on. |
| 72 | + |
| 73 | +2. Start the server: |
| 74 | + |
| 75 | +:: |
| 76 | + |
| 77 | + await push_server.start() |
| 78 | + |
| 79 | +3. Define a callback function: |
| 80 | + |
| 81 | +:: |
| 82 | + |
| 83 | + def callback_func(source_device, action, params): |
| 84 | + _LOGGER.info("callback '%s' from '%s', params: '%s'", action, source_device, params) |
| 85 | + |
| 86 | +4. Register the miio device to the server and its callback function to receive events from this device: |
| 87 | + |
| 88 | +:: |
| 89 | + |
| 90 | + push_server.register_miio_device(miio_device, callback_func) |
| 91 | + |
| 92 | +5. Create an :class:`PushServer.EventInfo` (:ref:`how to obtain event info<obtain_event_info>`) |
| 93 | + object with the event to subscribe to: |
| 94 | + |
| 95 | +:: |
| 96 | + |
| 97 | + event_info = EventInfo( |
| 98 | + action="alarm_triggering", |
| 99 | + extra="[1,19,1,111,[0,1],2,0]", |
| 100 | + trigger_token=miio_device.token, |
| 101 | + ) |
| 102 | + |
| 103 | +6. Send a message to the device to subscribe for the event to receive messages on the push_server: |
| 104 | + |
| 105 | +:: |
| 106 | + |
| 107 | + push_server.subscribe_event(miio_device, event_info) |
| 108 | + |
| 109 | +7. The callback function should now be called whenever a matching event occurs. |
| 110 | + |
| 111 | +8. You should stop the server when you are done with it. |
| 112 | + This will automatically inform all devices with event subscriptions |
| 113 | + to stop sending more events to the server. |
| 114 | + |
| 115 | +:: |
| 116 | + |
| 117 | + push_server.stop() |
| 118 | + |
| 119 | + |
| 120 | +.. _obtain_event_info: |
| 121 | + |
| 122 | +Obtaining Event Information |
| 123 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 124 | + |
| 125 | +When you want to support a new type of event in python-miio, |
| 126 | +you need to first perform a packet capture of the mobile Xiaomi Home app |
| 127 | +to retrieve the necessary information for that event. |
| 128 | + |
| 129 | +1. Prepare your system to capture traffic between the gateway device and your mobile phone. You can, for example, use `BlueStacks emulator <https://www.bluestacks.com>`_ to run the Xiaomi Home app, and `WireShark <https://www.wireshark.org>`_ to capture the network traffic. |
| 130 | +2. In the Xiaomi Home app go to `Scene` --> `+` --> for "If" select the device for which you want to make the new event |
| 131 | +3. Select the event you want to add |
| 132 | +4. For "Then" select the same gateway as the Zigbee device is connected to (or the gateway itself). |
| 133 | +5. Select the any action, e.g., "Control nightlight" --> "Switch gateway light color", |
| 134 | + and click the finish checkmark and accept the default name. |
| 135 | +6. Repeat the steps 3-5 for all new events you want to implement. |
| 136 | +7. After you are done, you can remove the created scenes from the app and stop the traffic capture. |
| 137 | +8. You can use `devtools/parse_pcap.py` script to parse the captured PCAP files. |
| 138 | + |
| 139 | +:: |
| 140 | + |
| 141 | + python devtools/parse_pcap.py <pcap file> --token <token of your gateway> |
| 142 | + |
| 143 | + |
| 144 | +.. note:: |
| 145 | + |
| 146 | + Note, you can repeat `--token` parameter to list all tokens you know to decrypt traffic from all devices: |
| 147 | + |
| 148 | +10. You should now see the decoded communication of between the Xiaomi Home app and your gateway. |
| 149 | +11. You should see packets like the following in the output, |
| 150 | + the most important information is stored under the `data` key: |
| 151 | + |
| 152 | +:: |
| 153 | + |
| 154 | + { |
| 155 | + "id" : 1234, |
| 156 | + "method" : "send_data_frame", |
| 157 | + "params" : { |
| 158 | + "cur" : 0, |
| 159 | + "data" : "[[\"x.scene.1234567890\",[\"1.0\",1234567890,[\"0\",{\"src\":\"device\",\"key\":\"event.lumi.sensor_magnet.aq2.open\",\"did\":\"lumi.123456789abcde\",\"model\":\"lumi.sensor_magnet.aq2\",\"token\":\"\",\"extra\":\"[1,6,1,0,[0,1],2,0]\",\"timespan\":[\"0 0 * * 0,1,2,3,4,5,6\",\"0 0 * * 0,1,2,3,4,5,6\"]}],[{\"command\":\"lumi.gateway.v3.set_rgb\",\"did\":\"12345678\",\"extra\":\"[1,19,7,85,[40,123456],0,0]\",\"id\":1,\"ip\":\"192.168.1.IP\",\"model\":\"lumi.gateway.v3\",\"token\":\"encrypted0token0we0need000000000\",\"value\":123456}]]]]", |
| 160 | + "data_tkn" : 12345, |
| 161 | + "total" : 1, |
| 162 | + "type" : "scene" |
| 163 | + } |
| 164 | + } |
| 165 | + |
| 166 | + |
| 167 | +12. Now, extract the necessary information form the packet capture to create :class:`PushServer.EventInfo` objects. |
| 168 | + |
| 169 | +13. Locate the element containing `"key": "event.*"` in the trace, |
| 170 | + this is the event triggering the command in the trace. |
| 171 | + The `action` of the `EventInfo` is normally the last part of the `key` value, e.g., |
| 172 | + `open` (from `event.lumi.sensor_magnet.aq2.open`) in the example above. |
| 173 | + |
| 174 | +14. The `extra` parameter is the most important piece containing the event details, |
| 175 | + which you can directly copy from the packet capture. |
| 176 | + |
| 177 | +:: |
| 178 | + |
| 179 | + event_info = EventInfo( |
| 180 | + action="open", |
| 181 | + extra="[1,6,1,0,[0,1],2,0]", |
| 182 | + ) |
| 183 | + |
| 184 | + |
| 185 | +.. note:: |
| 186 | + |
| 187 | + The `action` is an user friendly name of the event, can be set arbitrarily and will be received by the server as the name of the event. |
| 188 | + The `extra` is the identification of the event. |
| 189 | + |
| 190 | +Most times this information will be enough, however the :class:`miio.EventInfo` class allows for additional information. |
| 191 | +For example, on Zigbee sub-devices you also need to define `source_sid` and `source_model`, |
| 192 | +see :ref:`button press <_button_press_example>` for an example. |
| 193 | +See the :class:`PushServer.EventInfo` for more detailed documentation. |
| 194 | + |
| 195 | + |
| 196 | +Examples |
| 197 | +-------- |
| 198 | + |
| 199 | +Gateway alarm trigger |
| 200 | +~~~~~~~~~~~~~~~~~~~~~ |
| 201 | + |
| 202 | +The following example shows how to create a push server and make it to listen for alarm triggers from a gateway device. |
| 203 | +This is proper async python code that can be executed as a script. |
| 204 | + |
| 205 | + |
| 206 | +.. literalinclude:: examples/push_server/gateway_alarm_trigger.py |
| 207 | + :language: python |
| 208 | + |
| 209 | + |
| 210 | + |
| 211 | +.. _button_press_example: |
| 212 | + |
| 213 | +Button press |
| 214 | +~~~~~~~~~~~~ |
| 215 | + |
| 216 | +The following examples shows a more complex use case of acting on button presses of Aqara Zigbee button. |
| 217 | +Since the source device (the button) differs from the communicating device (the gateway), |
| 218 | +some additional parameters are needed for the :class:`PushServer.EventInfo`: `source_sid` and `source_model`. |
| 219 | + |
| 220 | +.. literalinclude:: examples/push_server/gateway_button_press.py |
| 221 | + :language: python |
| 222 | + |
| 223 | + |
| 224 | +:py:class:`API <miio.push_server>` |
0 commit comments