HomeAutomation

Sleep automation with Mi Band and Home Assistant

Have you ever thought about using your Mi Band to help you automate stuff? Read this article and you will know how you can use your Mi Band sleep detection to trigger automations in your Home Assistant!

You will need:

  • Tasker
  • Notify & Fitness for Mi Band
  • A Mi Band
  • An automation which creates a webhook and then publishes your sleep status on a MQTT topic
  • A sensor based on the MQTT topic previously defined (it can be avoided for the purpose of this automation but I prefer to have it to keep track of the sleep status)
  • Tasker profiles which react to your sleep status

Create a sleep_status.yaml file in your packages directory with the following content.

Replace [YOUR_NAME] and [YOUR_WEBHOOK_ID].

homeassistant:

automation:
  - alias: [YOUR_NAME] Sleep Status
    initial_state: true
    trigger:
      - platform: webhook
      webhook_id: [YOUR_WEBHOOK_ID]
      condition: []
    action:
      - service: mqtt.publish
        data_template: 
          payload: "{\"sleepStatus\": \"{{trigger.json.sleepStatus}}\"}"
          topic: "sleepstatus/[YOUR_NAME]"

sensor:
  - platform: mqtt
    name: "[YOUR_NAME] Sleep Status"
    state_topic: "sleepstatus/[YOUR_NAME]"
    value_template: '{{ value_json["sleepStatus"] }}'

Take now your phone, open Tasker and setup the following profiles which will send your sleep status events (when you wake up and fall asleep) to your Home Assistant.

Replace [YOUR_HOME_ASSISTANT_DOMAIN_NAME] and [YOUR_WEBHOOK_ID]

<TaskerData sr="" dvi="1" tv="5.9.rc">
<Profile sr="prof61" ve="2">
<cdate>1574199690392</cdate>
<clp>true</clp>
<edate>1574529186304</edate>
<flags>8</flags>
<id>61</id>
<mid0>63</mid0>
<nme>MiBand Fell Asleep</nme>
<Event sr="con0" ve="2">
<code>599</code>
<Str sr="arg0" ve="3">com.mc.miband.tasker.fellAsleep</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="0"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
</Event>
</Profile>
<Task sr="task63">
<cdate>1574199712492</cdate>
<edate>1574529096550</edate>
<id>63</id>
<nme>Send Asleep Payload</nme>
<pri>6</pri>
<Action sr="act0" ve="7">
<code>339</code>
<Bundle sr="arg0">
<Vals sr="val">
<net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%http_data
Data
Data that the server responded from the HTTP request.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1>%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2>%http_response_code
Response Code
The HTTP Code the server responded</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3>%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4>%http_response_length
Response Length
The size of the response in bytes</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
</Vals>
</Bundle>
<Int sr="arg1" val="1"/>
<Str sr="arg2" ve="3">https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]y</Str>
<Str sr="arg3" ve="3">Content-Type:application/json</Str>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3">{ "sleepStatus" : "Asleep" }</Str>
<Str sr="arg6" ve="3"/>
<Str sr="arg7" ve="3"/>
<Int sr="arg8" val="30"/>
<Int sr="arg9" val="0"/>
</Action>
</Task>
</TaskerData>
<TaskerData sr="" dvi="1" tv="5.9.rc">
<Profile sr="prof66" ve="2">
<cdate>1574200024388</cdate>
<edate>1574529166065</edate>
<flags>8</flags>
<id>66</id>
<mid0>65</mid0>
<nme>MiBand WokeUp</nme>
<Event sr="con0" ve="2">
<code>599</code>
<Str sr="arg0" ve="3">com.mc.miband.tasker.wokeUp</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="0"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
</Event>
</Profile>
<Task sr="task65">
<cdate>1574199886906</cdate>
<edate>1574529090100</edate>
<id>65</id>
<nme>Send Awake Payload</nme>
<pri>6</pri>
<Action sr="act0" ve="7">
<code>339</code>
<Bundle sr="arg0">
<Vals sr="val">
<net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%http_data
Data
Data that the server responded from the HTTP request.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1>%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2>%http_response_code
Response Code
The HTTP Code the server responded</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3>%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4>%http_response_length
Response Length
The size of the response in bytes</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
<net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
</Vals>
</Bundle>
<Int sr="arg1" val="1"/>
<Str sr="arg2" ve="3">https://[YOUR_HOME_ASSISTANT_DOMAIN_NAME]/api/webhook/[YOUR_WEBHOOK_ID]</Str>
<Str sr="arg3" ve="3">Content-Type:application/json</Str>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3">{ "sleepStatus" : "Awake" }</Str>
<Str sr="arg6" ve="3"/>
<Str sr="arg7" ve="3"/>
<Int sr="arg8" val="30"/>
<Int sr="arg9" val="0"/>
</Action>
</Task>
</TaskerData>

Now, you can create a Node-RED flow which reacts to sensor.[YOUR_NAME]_sleep_status with Awake/Asleep value and executes your preferred stuff (e.g. turn off all the lights as soon as you fall asleep).

I have used my Mi Band 2 and it detects my sleep activity usually within 5 minutes so don’t use it for real-time automations.

Edit: it works on my Mi Band 4, too!

Let me know what automations you are going to create!

HomeAutomation

Light control using an Aqara Wireless Remote Switch with Home Assistant and Node-RED

Home automation is nice, sending commands with your voice is very cool, but sometimes, especially at night, you might want to just use traditional methods like a wall switch. You can do it very easily without compromising your cool voice controls, and you can also customize the way it works to suit your needs!

smart_light

Let’s start, this is what you need:

The Aqara Wireless Remote Switch is a switch you can place anywhere because it doesn’t need any wire, it works with a small battery which will last a very long time. To make the following configuration you need to use the double rocker one: it has a lot of different click interactions you can customize.

This is the configuration you can have using the Node-RED flow shown in this post:

  • Left click: turn on/off the lights (toggle)
  • Right click: switch between a predefined set of colors
  • Left long click: decrease brightness
  • Right long click: increase brightness

This Node-RED flow should be pretty straightforward, anyway, let’s have some more details.

The first node is an event state node which reacts to the changes of the Aqara Switch: it can have different values like left, right, left_long, right_long, etc. Home Assistant detects it as a sensor.

The second node is a switch used to take different actions based on the type of click done on the Aqara switch.

Left Click scenario

A service call to toggle the lights, very easy!

Right Click scenario

This is the most interesting part, the state machine node! It is used to create a finite state machine of the colors and transitions you want the lights to have. Unfortunately, I couldn’t find a way to make the state machine move to the next state based only on the current one, without taking care of the input value, so I managed to achieve it with a workaround!

After the state machine node there is a switch node which checks if the state machine already did its transition checking a value which is put into the payload object in the following function node. This function node also put the current state machine value as payload of the message, so the state machine can correctly move. In doing so, the second time the flow will go to another function node which generates the light settings based on the value the state machine now has. Then, the last node simply sets this configuration to the lights.

Left long click scenario

A current state node which gets the current brightness of the light followed by a function node which calculates the increased brightness value. In the function node you can customize the brightness interval and the minimum brightness you would like to reach. The last node is a service call which sets the new brightness to the light.

Right long click scenario

Same as the left long click scenario but this time the light brightness is increased, so the function node calculates the increased value the light will have. Customization for brightness interval and maximum brightness are available here as well.

Enjoy your wireless switch!

smart switch node red flow
[
    {
        "id": "63038ca5.043d84",
        "type": "server-state-changed",
        "z": "ebd2b060.28c27",
        "name": "SmartSwitch1",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "entityidfilter": "sensor.smartswitch1_click",
        "entityidfiltertype": "exact",
        "outputinitially": false,
        "state_type": "str",
        "haltifstate": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "outputs": 1,
        "output_only_on_state_change": true,
        "x": 110,
        "y": 160,
        "wires": [
            [
                "e4f033f6.02139"
            ]
        ]
    },
    {
        "id": "e4f033f6.02139",
        "type": "switch",
        "z": "ebd2b060.28c27",
        "name": "click is",
        "property": "payload",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "left",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "right",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "left_long",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "right_long",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 4,
        "x": 290,
        "y": 160,
        "wires": [
            [
                "c92d98e8.8de858"
            ],
            [
                "bbcdb78.3712348"
            ],
            [
                "752f2116.e5b66"
            ],
            [
                "a10c6932.4ae3a8"
            ]
        ]
    },
    {
        "id": "c92d98e8.8de858",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "toggle living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "toggle",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 530,
        "y": 60,
        "wires": [
            []
        ]
    },
    {
        "id": "515a6b84.e21c54",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "set brightness to living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "turn_on",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1200,
        "y": 220,
        "wires": [
            []
        ]
    },
    {
        "id": "752f2116.e5b66",
        "type": "api-current-state",
        "z": "ebd2b060.28c27",
        "name": "",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "light.left_living_room",
        "state_type": "str",
        "state_location": "data",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "blockInputOverrides": false,
        "x": 560,
        "y": 200,
        "wires": [
            [
                "91a6b4b5.c19558"
            ]
        ]
    },
    {
        "id": "ba574398.cc6a",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "calculate increased brightness",
        "func": "const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MAX = 255;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness + BRIGHTNESS_INTERVAL\nif (brightness > BRIGHTNESS_MAX) {\n    brightness = BRIGHTNESS_MAX\n}\nmsg.payload = {\n    \"data\": {\n        \"brightness\":brightness\n        \n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 870,
        "y": 260,
        "wires": [
            [
                "515a6b84.e21c54"
            ]
        ]
    },
    {
        "id": "91a6b4b5.c19558",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "calculate decreased brightness",
        "func": "const BRIGHTNESS_INTERVAL = 100;\nconst BRIGHTNESS_MIN = 1;\n\nlet brightness = msg.data.attributes.brightness;\nlet entityId = msg.data.entity_id;\nbrightness = brightness - BRIGHTNESS_INTERVAL\nif (brightness < BRIGHTNESS_MIN) {\n    brightness = BRIGHTNESS_MIN\n}\nmsg.payload = {\n    \"data\": {\n        \"brightness\":brightness\n        \n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 870,
        "y": 200,
        "wires": [
            [
                "515a6b84.e21c54"
            ]
        ]
    },
    {
        "id": "a10c6932.4ae3a8",
        "type": "api-current-state",
        "z": "ebd2b060.28c27",
        "name": "",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "override_topic": false,
        "entity_id": "light.left_living_room",
        "state_type": "str",
        "state_location": "data",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "blockInputOverrides": false,
        "x": 560,
        "y": 260,
        "wires": [
            [
                "ba574398.cc6a"
            ]
        ]
    },
    {
        "id": "bbcdb78.3712348",
        "type": "state-machine",
        "z": "ebd2b060.28c27",
        "name": "bulb color state",
        "triggerProperty": "payload",
        "triggerPropertyType": "msg",
        "stateProperty": "topic",
        "statePropertyType": "msg",
        "outputStateChangeOnly": false,
        "throwException": false,
        "states": [
            "white",
            "yellow"
        ],
        "transitions": [
            {
                "name": "white",
                "from": "white",
                "to": "yellow"
            },
            {
                "name": "yellow",
                "from": "yellow",
                "to": "white"
            }
        ],
        "x": 540,
        "y": 140,
        "wires": [
            [
                "10c6a2fd.57b7dd"
            ]
        ]
    },
    {
        "id": "f1efc70d.5de9e8",
        "type": "api-call-service",
        "z": "ebd2b060.28c27",
        "name": "set color to living room lights",
        "server": "b0807fb3.bda6d",
        "version": 1,
        "service_domain": "light",
        "service": "turn_on",
        "entityId": "group.living_room_lights",
        "data": "",
        "dataType": "json",
        "mergecontext": "",
        "output_location": "",
        "output_location_type": "none",
        "mustacheAltTags": false,
        "x": 1340,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "10c6a2fd.57b7dd",
        "type": "switch",
        "z": "ebd2b060.28c27",
        "name": "has state ben processed",
        "property": "data.attributes.state_machine_processed",
        "propertyType": "msg",
        "rules": [
            {
                "t": "true"
            },
            {
                "t": "else"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 770,
        "y": 140,
        "wires": [
            [
                "41cecb0c.bbfbe4"
            ],
            [
                "f4044d2d.8f0a6"
            ]
        ]
    },
    {
        "id": "f4044d2d.8f0a6",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "set topic as payload",
        "func": "msg.payload = msg.topic\nmsg.data = {\n    \"attributes\": {\n        \"state_machine_processed\" : true\n    }\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 1040,
        "y": 140,
        "wires": [
            [
                "bbcdb78.3712348"
            ]
        ]
    },
    {
        "id": "41cecb0c.bbfbe4",
        "type": "function",
        "z": "ebd2b060.28c27",
        "name": "create color data as payload",
        "func": "whitePayload = {\n    \"data\": {\n        \"kelvin\": 6000\n    }\n}\n\nyellowPayload = {\n    \"data\": {\n        \"kelvin\": 3000\n    }\n}\n\nlet payloadToUse;\nswitch(msg.topic) {\n    case \"white\": \n        payloadToUse = whitePayload;\n        break;\n    case \"yellow\":\n        payloadToUse = yellowPayload;\n        break;\n    default:\n    \n        break;\n}\n\nmsg.payload = payloadToUse;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "x": 1060,
        "y": 100,
        "wires": [
            [
                "f1efc70d.5de9e8"
            ]
        ]
    },
    {
        "id": "b0807fb3.bda6d",
        "type": "server",
        "z": "",
        "name": "Home Assistant"
    }
]
Android

Enable the new Android Auto UI

In the last few weeks the new Android Auto UI is being released by Google but it’s not yet available for everyone. There are some tricks you can use to force enable it before Google releases it to everyone.

One of these tricks is explained here on Reddit.

It works on my OP6 running Android Pie, but those commands need to be executed each time I want to use the new Android Auto UI, otherwise I see the old one.

To overcome this little issue I made a Tasker profile to automatically run those commands when the smartphone is connected to a power source, so they are executed before Android Auto starts.

Here the Tasker profile! Just import it in your Tasker and enjoy the new Android Auto UI!

<TaskerData sr="" dvi="1" tv="5.8.3">
<Profile sr="prof21" ve="2">
<cdate>1566724247086</cdate>
<edate>1566724464417</edate>
<id>21</id>
<mid0>17</mid0>
<mid1>17</mid1>
<nme>On power change AA to new UI</nme>
<State sr="con0" ve="2">
<code>10</code>
<Int sr="arg0" val="0"/>
</State>
</Profile>
<Task sr="task17">
<cdate>1566723956355</cdate>
<edate>1566724006352</edate>
<id>17</id>
<nme>New AA UI</nme>
<Action sr="act0" ve="7">
<code>123</code>
<Str sr="arg0" ve="3">sqlite3 /data/data/com.google.android.gms/databases/phenotype.db "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, user, name, intVal, committed) VALUES ('com.google.android.projection.gearhead',45592854,0,0,'','Boardwalk__launch_experiment_id',15592854,1);"</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="1"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3"/>
</Action>
<Action sr="act1" ve="7">
<code>123</code>
<Str sr="arg0" ve="3">sqlite3 /data/data/com.google.android.gms/databases/phenotype.db "INSERT OR REPLACE INTO Flags (packageName, version, flagType, partitionId, user, name, boolVal, committed) VALUES ('com.google.android.projection.gearhead',45592854,0,0,'','Boardwalk__enabled',1,1);"</Str>
<Int sr="arg1" val="0"/>
<Int sr="arg2" val="1"/>
<Str sr="arg3" ve="3"/>
<Str sr="arg4" ve="3"/>
<Str sr="arg5" ve="3"/>
</Action>
</Task>
</TaskerData>
HomeAutomation

Make your washing machine smarter with Home Assistant

washing machines

Do you own an old washing machine and do you ever think about how to make your washing machine smarter? Would you have better information about the current washing cycle?

Well, you can do it today, you just need a couple of things:

  • a smart plug with energy monitoring feature
  • an Home Assistant instance running somewhere (e.g. on your raspberry)

SMART PLUG INTEGRATION

Each smart plug is different from another so you have to figure out how to integrate your into Home Assistant.

In my setup I used a TP-Link HS-110 and I integrated it into my Home Assistant as shown here.

After the successful integration you should have a new sensor which reports the current consumption, and this is the one you are going to use to monitor your washing machine!

WASHING MACHINE INTEGRATION

The following snippet of code is an Home Assistant package, you can just copy-paste it into a whatever_you_want.yaml file into your packages directory; the only thing you need to do is to replace PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE with your current consumption sensor name!

homeassistant:
  customize_glob:
    "*washing_machine*":
      icon: mdi:washing-machine

input_text:
  washing_machine_enriched_status:
    name: 'Washing Machine Enriched Status'
    initial: Off
    
sensor:
  - platform: template
    sensors:
      washing_machine_status:
        friendly_name: 'Washing Machine Status'
        value_template:  >
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 3.0 %}
            {{ "Running" }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 1 %}
            {{ "On" }}
          {% else %}
            {{ "Off" }}
          {% endif %}
      washing_machine_info:
        friendly_name: 'Washing Machine Info'
        entity_id: sensor.time
        value_template:  >
          {%- macro as_formatted_elapsed_time(now, other_date_time) %}
          {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
          {% set seconds = (duration % 60) | int %}
          {% set minutes = ((duration / 60) | int) % 60 %}
          {% set hours = (duration / 3600) | int %}
          {{ [hours, "hours", minutes, "minutes", seconds, "seconds"] | join(' ') }}
          {%- endmacro %}
          {% if states.input_text.washing_machine_enriched_status.state == "Running" %}
            Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Running" %}
            Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Off" %}
            {{ "Washing machine ready to start" }}
          {% else %}
            {{ "Washing machine is off" }}
          {% endif %}

automation:
  - id: '1556314846684'
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  >
            {% if trigger.to_state.state == "Running" %}
              {{ "Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Running" %}
              {{ "On_After_Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Off" %}
              {{ "On_After_Off" }}
            {% else %}
              {{ "Off" }}
            {% endif %}

DETAILED EXPLANATION

Let me explain a bit about what this package does:

Here we are just telling Home Assistant to use a washing machine icon for all the sensors containing the string washing_machine

homeassistant:
  customize_glob:
    "*washing_machine*":
      icon: mdi:washing-machine

Here we declare a sensor, washing_machine_status, to keep track of the status of the washing machine. There are three possible status: Running, On and Off. You would need to tune the watts value according to your washing machine to detect when it’s running or just turned on.

washing_machine_status:
        friendly_name: 'Washing Machine Status'
        value_template:  >
          {% if states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 3.0 %}
            {{ "Running" }}
          {% elif states.sensor.PUT_YOUR_ENERGY_CONSUMPTION_SENSOR_NAME_HERE .state | float > 1 %}
            {{ "On" }}
          {% else %}
            {{ "Off" }}
          {% endif %}

Here we setup an automation to store in an input_text (couldn’t find a better way) the enriched status of our washing machine: the enriched status is a status which keeps track of the previous one. So for example, if the washing machine was off and you turn it on, the status would be On, but the enriched status would be On_After_Off

automation:
  - id: '1556314846684'
    alias: Refresh Washing Machine Info
    initial_state: true
    trigger:
      platform: state
      entity_id: sensor.washing_machine_status
    action:
      - service: input_text.set_value
        data_template:
          entity_id: input_text.washing_machine_enriched_status
          value:  >
            {% if trigger.to_state.state == "Running" %}
              {{ "Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Running" %}
              {{ "On_After_Running" }}
            {% elif trigger.to_state.state == "On" and trigger.from_state.state == "Off" %}
              {{ "On_After_Off" }}
            {% else %}
              {{ "Off" }}
            {% endif %}

This is just the simple declaration of the variable holding the enriched status of the washing machine.

input_text:
  washing_machine_enriched_status:
    name: 'Washing Machine Enriched Status'
    initial: Off

Here we declare another sensor, washing_machine_info, which will keep us informed of the washing machine’s current status and will also tell us more information (e.g. for how long the washing machine is running). The sensor will be updated every minute since it has a dependency on sensor.time: you can customize this behaviour adding another automation to refresh it faster or slower, but I think every minute is enough. as_formatted_elapsed_time is a macro used to display the elapsed time between two moments.

     washing_machine_info:
        friendly_name: 'Washing Machine Info'
        entity_id: sensor.time
        value_template:  >
          {%- macro as_formatted_elapsed_time(now, other_date_time) %}
          {% set duration = as_timestamp(now) - as_timestamp(other_date_time) %}
          {% set seconds = (duration % 60) | int %}
          {% set minutes = ((duration / 60) | int) % 60 %}
          {% set hours = (duration / 3600) | int %}
          {{ [hours, "hours", minutes, "minutes", seconds, "seconds"] | join(' ') }}
          {%- endmacro %}
          {% if states.input_text.washing_machine_enriched_status.state == "Running" %}
            Washing machine running for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Running" %}
            Clothes left in the washing machine for: {{ as_formatted_elapsed_time(now(), states.input_text.washing_machine_enriched_status.last_changed)}}
          {% elif states.input_text.washing_machine_enriched_status.state == "On_After_Off" %}
            {{ "Washing machine ready to start" }}
          {% else %}
            {{ "Washing machine is off" }}
          {% endif %}

Finally, you can just put your new sensor washing_machine_info into your UI and you will get very useful information about your washing machine, making it actually smarter.

Furthermore, you can use the statuses we collect here, and their time-based changes, to make other automations like send you a Telegram notification when the washing machine cycle ends, but at the moment that’s left to your imagination!

Android

Build your smart car’s on-board computer!

Reading this post you will be able to create your smart car’s on-board computer, for example you will be to able to ask your Google Assistant (from your device or from an Android Auto head unit) the temperature of your car’s coolant liquid! Woa!

smartcar lights

 

What do you need:

  • an Android device (with Google Assistant or Android Auto since your going to use this in your car)
  • Torque
  • Tasker
  • Join
  • An IFTTT account
  • an OBD2 device

Such a long list of stuff!

Let’s start!

IFTTT AND TASKER INTEGRATION

Thanks to Join, a new app from joaomgcd, you can create a URL to push information to your devices in a very easy and powerful way! Read his well written tutorial here  and you will be able to generate a URL to use in the Webhook that action of IFTTT.

As reference, I’d like to report the old way of doing this, so everyone can read it and understand how much Join has simplified and improved this process!

TASKER AND TORQUE INTEGRATION

Now, you will need to make Tasker able to read your car’s data using Torque. To accomplish this, open Torque and set it up to log your car’s data to a file on your sdcard (you can do it making changes in the Settings – Data Logging and Upload section)

Now Tasker will be able to read your car’s data reading that log file using the following task which you could import into it.

<TaskerData sr="" dvi="1" tv="5.2.bf6">
  <Task sr="task43">
    <cdate>1432225457845</cdate>
    <edate>1531597970703</edate>
    <id>43</id>
    <nme>ReadCarEngineCoolantTemperature</nme>
    <pri>100</pri>
    <Action sr="act0" ve="7">
      <code>342</code>
      <Int sr="arg0" val="5"/>
      <Str sr="arg1" ve="3">/storage/emulated/0/torqueLogs/trackLog.csv</Str>
      <Str sr="arg2" ve="3">%TORQUE_LOG_EXISTS</Str>
      <Int sr="arg3" val="0"/>
    </Action>
    <Action sr="act1" ve="7">
      <code>123</code>
      <se>false</se>
      <Str sr="arg0" ve="3">tail -1 /storage/emulated/0/torqueLogs/trackLog.csv</Str>
      <Int sr="arg1" val="0"/>
      <Int sr="arg2" val="0"/>
      <Str sr="arg3" ve="3">%OBDLOG</Str>
      <Str sr="arg4" ve="3"/>
      <Str sr="arg5" ve="3"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act2" ve="7">
      <code>590</code>
      <Str sr="arg0" ve="3">%OBDLOG</Str>
      <Str sr="arg1" ve="3">,</Str>
      <Int sr="arg2" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act3" ve="7">
      <code>547</code>
      <Str sr="arg0" ve="3">%EngineCoolantTemp</Str>
      <Str sr="arg1" ve="3">%OBDLOG2</Str>
      <Int sr="arg2" val="0"/>
      <Int sr="arg3" val="0"/>
      <Int sr="arg4" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act4" ve="7">
      <code>559</code>
      <Str sr="arg0" ve="3">Engine coolant temperature is %EngineCoolantTemp degrees</Str>
      <Str sr="arg1" ve="3">default:default</Str>
      <Int sr="arg2" val="3"/>
      <Int sr="arg3" val="5"/>
      <Int sr="arg4" val="5"/>
      <Int sr="arg5" val="1"/>
      <Int sr="arg6" val="0"/>
      <Int sr="arg7" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>true</rhs>
        </Condition>
      </ConditionList>
    </Action>
    <Action sr="act5" ve="7">
      <code>559</code>
      <Str sr="arg0" ve="3">E C U connection not established!</Str>
      <Str sr="arg1" ve="3">default:default</Str>
      <Int sr="arg2" val="3"/>
      <Int sr="arg3" val="5"/>
      <Int sr="arg4" val="5"/>
      <Int sr="arg5" val="1"/>
      <Int sr="arg6" val="0"/>
      <Int sr="arg7" val="0"/>
      <ConditionList sr="if">
        <Condition sr="c0" ve="3">
          <lhs>%TORQUE_LOG_EXISTS</lhs>
          <op>0</op>
          <rhs>false</rhs>
        </Condition>
      </ConditionList>
    </Action>
  </Task>
</TaskerData>

This task will check for that log file on your external memory and:

  • if it founds it, it make your speakers say the last engine coolant temperature read by Torque
  • if it doesn’t found it, it make your speakers say there is no connection between Torque and you car ECU (your OBD2 adapter).

Another task that you will need is one that deletes that log file when the Torque app closes, so the next time you ask Tasker to read that file your are sure the data is new, and if there is no file, a new connection with your car wasn’t established.

<TaskerData sr="" dvi="1" tv="5.2.bf6">
  <Task sr="task47">
    <cdate>1525023939110</cdate>
    <edate>1529001057169</edate>
    <id>47</id>
    <nme>DeleteTorqueLog</nme>
    <pri>100</pri>
    <Action sr="act0" ve="7">
      <code>406</code>
      <Str sr="arg0" ve="3">torqueLogs/trackLog.csv</Str>
      <Int sr="arg1" val="0"/>
      <Int sr="arg2" val="0"/>
    </Action>
  </Task>
</TaskerData>

GOOGLE ASSISTANT AND IFTTT AND TASKER AND TORQUE INTEGRATION

Now the last part! We have all the pieces, let’s put them together!

Login to IFTTT and create a new applet!

Click on this and look for Google Assistant, then click on it. Select the Say a simple phrase trigger and write your custom question(s) and response; click then on Create trigger

Click now on that and look for Webhooks, then click on it. Select the Make a web request action and use the URL you generated as explained in the joaomgcd’s tutorial. Click on Create action, then on Finish and your recipe is ready!

Open your Google Assistant and say the phrase you just put in the IFTTT recipe, your device should say you’re not connected to the ECU, you got the push notification, that’s great!

Now go to your car, turn it on, make Torque pair with your OBD2 adapter…. when it’s ready ask Google Assistant again to read your engine coolant temperature and now you should hear it! Wonderful, it works!!!

FURTHER IMPROVEMENTS OF YOUR SMART CAR

The recipe on IFTTT and the task on Tasker could be improved by using parameters and so making you able to ask for and listen to different details of your car, which could now be called a smart car!

Android

AndroidAutoAlerts – Simple alerts app for Android Auto

AndroidAutoAlerts is a very simple app for Android Auto I have developed to test my Android Auto unit. Please read through the article to check it out!

What it does and how it works

As I said, the app is very simple and at the moment it has only one functionality: it will notify the driver if the speed limit is exceeded. Wow!

The driver can set the maximum speed limit through the app’s settings (you can also choose the name of your virtual assistant!)

androidautoalerts settings

Then, go back to the main screen and start the notification service. A service will run (and will keep running in the foreground using a system notification when you exit the app or attach your device to your Android Auto unit).

androidautoalerts mainscreenandroidautoalerts foregroundnotification

So, now, using the speed taken from the GPS sensor, when the app catches you going faster a notification will be displayed on your Android Auto unit from your virtual assistant! Tap it and listen to what it has to say to you! Please slow down, you have exceeded your speed limit!!

androidautoalerts notification

 

Where to get it

At the moment, the app is not following all of the Android Auto requirements (see them here) and Google only allows music and messaging Android Auto apps. For this reason I had to create the notification like a messaging app, like you are going to reply to it, even if you are not. Furthermore, I’m not going to publish it on Google Play at the moment (till apps of this kink will be accepted on it), but you can check its source code which I shared here on github.

Future development

Let’s wait for Google to accept other kinds of apps on the Android Auto platform and let’s hope this platform will become more and more interactive, full of useful apps (maybe some which can control car’s features, would be amazing!) and used by more and more people! I already love it, can’t live without it!

Development, Java, Spring

Using Spring Data to persist a Set of Enums in a Many-To-Many relationship

Usually, when we write a Many-To-Many relationship it is between 2 entities. Sometimes, it could happen that we have an entity which has a field which is a set of enum. In this case, we need to approach the problem differently and Spring comes to help.

Let’s see an example where we have two classes: Person and Skill (e.g. swimming, running, etc).

Many-To-Many between entities example

The following code shows what the situation is when Person and Skill are both entities.

public class Person {
  private Long id;
  private String name;
  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(name = "person_skill", joinColumns = {
            @JoinColumn(name = "person_id")}, inverseJoinColumns = {
            @JoinColumn(name = "skill_id")})
  private Set<Skill> skillSet;
}

public class Skill {
  private Long id;
  private String name;
}

To use this mapping, you would have 3 tables. You can create them with the following SQL code.

CREATE TABLE `person` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`));

CREATE TABLE `skill` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`));

CREATE TABLE `person_skill` (
  `person_id` BIGINT(20) NOT NULL,
  `skill_id` BIGINT(20) NOT NULL,
  PRIMARY KEY (`person_id`, `skill_id`),
  INDEX `skill_fk_idx` (`skill_id` ASC),
  CONSTRAINT `person_fk`
    FOREIGN KEY (`person_id`)
    REFERENCES `person` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE,
  CONSTRAINT `skill_fk`
    FOREIGN KEY (`skill_id`)
    REFERENCES `skill` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE);

 

Many-To-Many between entity and enum example

The following code shows what the situation is when Person is an entity and Skill is an enum.

public class Person { 
  private Long id; 
  private String name; 
  @ElementCollection(targetClass = Skill.class)
  @CollectionTable(name = "person_skill",
            joinColumns = @JoinColumn(name = "person_id"))
  @Enumerated(EnumType.STRING)
  @Column(name = "skill_id")
  private Set<Skill> skillSet;
} 

public enum Skill {
  RUNNING, SWIMMING
}

To use this mapping, you would have only 2 tables, because Skill is not an entity and would not have its own table. You can create them with the following SQL code.

CREATE TABLE `person` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`));

CREATE TABLE `person_skill` (
  `person_id` BIGINT(20) NOT NULL,
  `skill_name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`person_id`, `skill_name`),
  CONSTRAINT `person_fk`
    FOREIGN KEY (`person_id`)
    REFERENCES `person` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE);

As you can see here, you don’t have the table skill because Skill is not an entity but only an enum; you only have to create the table which represents the relationship between Person and Skill. Furthermore, since we added the annotation @Enumerated(EnumType.STRING) Spring Data will save the name of the entity in the database (look, we put a VARCHAR column). If you prefer, you could use EnumType.ORDINAL and Spring Data will save the ordinal value of the entity (1,2,3,etc..), so change the column to accept a numeric value.

That’s it!

sysadmin, tools, Windows

MobaXterm – Enhanced SSH client for Windows

I’ve always struggled myself each time I had to use SSH on Windows (yes, at least half of the time I use it, I like it). Just few months ago Windows released the Bash Ubuntu integration, which is very well integrated with the Windows environment and it’s a very cool feature, even if it is still a beta!

Anyway, since I was always looking for a better alternative than PuTTY, I finally found an alternative which is extremely worth to be mentioned, MobaXterm.

It has a very cool list of features and has both a free and a professional editions.

It is a lot more than only a featured SSH client, so, really,  give it a look, it’s really worth it!

Thank you MobaXterm!

Android, Development

Android Contextual Action Bar

In this post you will learn how to implement a Contextual Action Bar (CAB) which will be useful to do actions on multiple items you have selected in a RecyclerView.

The Contextual Action Mode represents a contextual mode of the user interface and focuses user interaction toward performing contextual actions. In the case of a RecyclerView which shows a list of item, Contextual Action Mode is triggered after a long press on one of this items: this causes the Contextual Action Bar to appear at the top of the screen so then the user can interact with its actions.

Let’s follow these steps!

Initial setup

First of all, create a RecyclerView and its adapter in the usual way you do it. Here in this example I will use a RecyclerView which shows Authentication items (it is a class I made for a project, it doesn’t matter for the purpose of the example, use whatever you want, and change the names accordingly).

Theme changes

Open your styles.xml file and add the following lines:

<!--  It should be true otherwise action mode will not overlay toolbar -->
       <item name="windowActionModeOverlay">true</item>
       <!--  For Custom Action Mode Background Color/Drawable -->
       <item name="actionModeBackground">@color/colorAccent</item>

Comments should be self-explanatory.

Menu file

We need a menu file which contains the available actions for that RecyclerView. Let’s create a file into the res/menu directory like the following:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_delete"
        android:icon="@drawable/ic_delete_white_24dp"
        android:title="@string/action_delete"
        app:showAsAction="always" />

</menu>

We added a delete action that will be shown on the CAB.

Adapter changes

Our adapter we created in the first step needs to be able to keep trace of the elements we select. To do this, let’s modify our adapter and create a new field

private final SparseBooleanArray selectedItemsIds;

and initialize it in our constructor

selectedItemsIds = new SparseBooleanArray();

Now, let’s create an Interface which will be useful for all the Adapters which need the CAB, and make our Adapter implements it. Please note this is a generic interface, so adapt it to the model you are displaying in your adapter.

public interface ActionModeAdapterCallbacks<T> {

    void toggleSelection(int position);

    void clearSelections();

    int getSelectedCount();

    List<T> getSelectedItems();
}

This is what the implementation should look like:

@Override
public void toggleSelection(final int position) {
    if (selectedItemsIds.get(position)) {
        selectedItemsIds.delete(position);
    } else {
        selectedItemsIds.put(position, true);
    }
    notifyItemChanged(position);
}

@Override
public void clearSelections() {
    selectedItemsIds.clear();
    notifyDataSetChanged();
}

@Override
public int getSelectedCount() {
    return selectedItemsIds.size();
}

@Override
public List<Authentication> getSelectedItems() {
    final List<Authentication> selectedItemList = new LinkedList<>();
    for (int i = 0; i < selectedItemsIds.size(); i++) {
        selectedItemList.add(authenticationList.get(selectedItemsIds.keyAt(i)));
    }
    return selectedItemList;
}

Now, we also need the Adapter to change the view state when an item gets selected to show the user a visual feedback. To do this we can use a StateListDrawable and in the onBindViewHolder() method add this line.

holder.itemView.setActivated(selectedItemsIds.get(position));

Create a new xml file inside the res/drawable directory:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/colorPrimaryDark" android:state_activated="true" />
    <item android:drawable="@android:color/transparent" />
</selector>

and assign it to the root view element of the layout file which represents your adapter’s row item:

android:background="@drawable/statelist_item_background"

Doing so, each time an item is selected or deselected, the adapter will change its color accordingly.

Fragment/Activity changes

Now, we need to make our Fragment/Activity class aware that we can trigger the Contextual Action Mode. To do so, let’s create an interface and call it ActionModeViewCallbacks.

public interface ActionModeViewCallbacks {

    void onListItemSelect(final int position);

    void onDestroyActionMode();

}

This interface has 2 methods:

  • onListItemSelect(final int position): to be used after a long click on an item to trigger the Action Mode (or after a single click on an item, if Action Mode was already triggered, to select this new item, too);
  • onDestroyActionMode(): reset actionMode variable to null;

Now, let’s create another interface which extends from this one:

public interface ListAuthenticationActionModeViewCallbacks extends ActionModeViewCallbacks {

    void onDeleteActionClicked();

}

This new interface has another method, onDeleteActionClicked(), which is where the presenter gets called (check MVP if you are not aware what MVP and a Presenter are) and asked to delete the items the user has selected (if you have more than a single action, you need to create more methods, each for any action you have).

This interface must be implemented by the Fragment/Activity which wants to use the Contextual Action Mode. The implementation looks like this:

@Override
    public void onListItemSelect(final int position) {
        listAuthenticationAdapter.toggleSelection(position);

        final boolean hasCheckedItems = listAuthenticationAdapter.getSelectedCount() > 0;

        if (hasCheckedItems && actionMode == null) {
            // there are some selected items, start the actionMode
            actionMode = ((AppCompatActivity) getActivity()).startSupportActionMode(new ListAuthenticationToolbarActionModeCallback(this, this, listAuthenticationAdapter));
        } else if (!hasCheckedItems && actionMode != null) {
            // there no selected items, finish the actionMode
            actionMode.finish();
        }

       if (actionMode != null) {
            //set action mode title on item selection
            actionMode.setTitle(getString(R.string.cab_selected, listAuthenticationAdapter.getSelectedCount()));
        }
        
    }

    @Override
    public void onDestroyActionMode() 
        actionMode = null;
    }


@Override
public void onDeleteActionClicked() {
 presenter.delete(listAuthenticationAdapter.getSelectedItems());
}

where R.string.cab_selected is

<string name="cab_selected">%1$d selected</string>

In doing startSupportActionMode() we need to pass a ActionMode.Callback item that I haven’t explained yet. Let’s create another class which implements that interface.

public class ListAuthenticationToolbarActionModeCallback implements ActionMode.Callback {

    private final ActionModeViewCallbacks actionModeViewCallbacks;
    private final ListAuthenticationAdapter listAuthenticationAdapter;

    public ListAuthenticationToolbarActionModeCallback(final ActionModeViewCallbacks actionModeViewCallbacks, final ListAuthenticationAdapter listAuthenticationAdapter) {
        this.actionModeViewCallbacks = actionModeViewCallbacks;
        this.listAuthenticationAdapter = listAuthenticationAdapter;
    }

    @Override
    public boolean onCreateActionMode(final ActionMode mode, final Menu menu) {
        mode.getMenuInflater().inflate(R.menu.listauthentication, menu);
        return true;
    }

    @Override
    public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) {
        menu.findItem(R.id.action_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
        return true;
    }

    @Override
    public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_delete:
                actionModeViewCallbacks.onDeleteActionClicked();
                mode.finish();
                return true;
        }
        return false;
    }

    @Override
    public void onDestroyActionMode(final ActionMode mode) {
        actionModeViewCallbacks.onDestroyActionMode();
        listAuthenticationAdapter.clearSelections();
    }
}

Please have a look on the official documentation here to understand how ActionMode.Callback works.

Screenshots

See the following screenshots:

  • first one shows the initial situation before Action Mode is triggered;
  • second one shows the situation after Action Mode was triggered by long press an item;
  • third one shows the final situation after the item “Auth 2” was deleted.

contextual action bar first contextual action bar selected contextual action bar after delete

Conclusions

Your Contextual Action Mode should work now! I suggest you to create different menu files, Fragment/ActivityToolbarActionModeCallback and Fragment/ActivityActionModeViewCallbacks to keep everything separated.

Please note: if you rotate the device, Contextual Action Mode is lost, you need to save its state and restore it. Another post will follow, hopefully soon!

Development, Java

Java concurrency: a few examples

Sometimes we write very high level code, sometimes we don’t. I want to put here a link to a simple project which shows how to deal with concurrency in Java with a few examples.

The project is available on Github here

Goal of the project is to retrieve the details and the balance of a player and show them at the same time: until you have collected both of them you have to wait before showing the user the information.

There are two different packages: in the first, Futures and Callables are being used, in the other, Threads/Runnables and a CountDownLatch are.

Futures and Callables are the best option when you need to run a task which has a return value. I also made it in a different way using Threads/Runnables, a CountDownLatch and some listeners but these classes are usually used when you have tasks which won’t return any value.

Use the two Main classes to run the examples. Have fun!