ESP32 Magic the Gathering Life Counter

By Xavi

So here we are once again!

Some days ago playing Magic The Gathering with some friends an issue arose. Seve, playing with cards that allow the player to go above the initial amount of lives or health points, went far beyond making difficult to track his health even with two 20 faces dice.

Luckily, at some point we acquired a few ESP32 boards and a couple of peripheral sets for arduino. The idea was to build a counter so that it could display in a screen both players’ health points removing the hassle of having to keep state with physical means like tokens or dice.

After some tinkering and hardware testing (one of the two screens we had laying around refused to work) and some iterations after, we achieved this result:

Default player names and joystick LED shows player Xavi is selected

LEDs indicate both player selected(who receives the input) and if desired, player’s active turn. On the upper part of the screen is shown who is playing against who, and at the bottom line, health points left. To control this rudimentary but useful tool, we initially thought on controlling it by buttons, but they needed another board to be placed and some kind of mold to ease their use so they were not extremely attractive to work with. In the end we picked up a joystick, it has 2 analogical buttons (axis x and y) and 1 digital button (the click) and only needs 3 cables of data. Win-win. The good thing about joysticks is even if they only have 2 analog buttons, those can be multiplied as needed. For every axis the micro-controller board makes a reading, which depending on micro-controller’s bit precision can be up to 1023, 2047, 4095, etc etc. Take in account 0 is the first value so it would be 2 power precision bits, minus 1 (2^n - 1). This is because the axis are actually potentiometers that provide those readings allowing more or less current to go through them depending on the angle of the joystick lever. If you normalize the readings considering 0 the at-rest position, then you have 3 possible readings per axis, negative, positive and 0.

When on normal mode, moving up and down (axis y) increases or decreases current player’s health points. Moving left and right switches player selected (change lit LED). When clicking, player’s name selection is toggled and both x axis and y axis are used to go through the list of preset player names for focused player. For now the list of player names is static but leveraging ESP32 network capabilities we will add a server to be able to edit dynamically the names from the phone or computer and even maybe get rid of the joystick and control the counter from them as well.

<presence /> at Berlin

By Seve

What did I do?

In short: The plan was to implement XEP-0382 in Gajim.

Gajim is a well-known XMPP client (and used by many people I know), so adding support for it made sense, right? I was lucky (very lucky) because Philipp Hörist (Gajim developer) attended as well, something decisive to get things done in a weekend. Thank you, Philipp!

At the beginning I started to look at writing it as a core feature, but Philipp warned me about Gajim going over a transition phase and my changes would not see the light until next release or so. Following his suggestion, I went for writing it as a plugin.

I must admit, it works with the current UI elements, but I’m not entirely satisfied with the result. I would like to give some thoughts to the button’s style that displays the content of the message, as well as if it would be possible to add another entry (text input) on top of the one for the message, instead of a floating element on the spoiler icon.

Letting the UI be, let’s take a look at the current parser:

# XEP-0382: Spoiler messages
def parse_spoiler(self, stanza):
    # Hardcoded namespace until nbxmpp implementation
    xml_lang = LANG
    spoiler_hint = None
    is_spoiler = False
    spoiler_elements = stanza.getTags(

    # If there's more than one spoiler element,
    # pick the one where matches with xml_lang
    if spoiler_elements:
        is_spoiler = True
        if len(spoiler_elements) == 1:
            spoiler_element = spoiler_elements[0]
            spoiler_element = stanza.getTag(
                attrs={'xml:lang': xml_lang},
        if spoiler_element:
            spoiler_hint = spoiler_element.getData()

    return is_spoiler, spoiler_hint
XEP-0382 allows for more than one <spoiler/> element in a message, using the attribute xml:lang. The decision made for this one was, if there’s more than one element, pick the hint the user can understand, using xml_lang (the client’s language). If there is no element with that language, the original hint is not displayed to the user and “Show message” is used instead (the same text as elements without hint). This is very open for discussion, because I would actually prefer to see the hint in whatever language it is. Be it because I speak more than one language and maybe one of those matches or because I could just translate it in case I would like to. Other people just find it confusing to display text in a language the application is not being displayed on (which is something easy to agree with), or even feel uncomfortable reading other alphabets that are not the one they know. And being honest, this would be used mainly by people speaking the same language, so this kind of problem should be marginal.
Nevertheless, working on this helped me to think a bit on the feature and if the specification needed changes before moving towards Draft.

I didn’t know much about the project internals, and even though the basic feature is done, there are some things that I will try to include eventually. For example,

  • I would have liked to toggle the spoiler on and off instead of revealing it until the chat window is reopened again.
  • Embedded images do not work (it makes me very sad). It is a separate plugin and I still need to check if it is even possible with the current implementation.
  • Link formatting. Currently transforming text into a clickable link does not work if it is inside a spoiler.
  • Popup notifications display the content of the message (they should not)

Currently, Gajim uses a TextView for the conversation window, which makes this kind of tweaks a bit more tedious than they could be if some other solution was used. But hopefully, we will get there! :)

I will keep improving the plugin and making sure it is maintained, so reach to me to pull my ears if that’s not the case!

The experience

Well, what can I say? Meeting with people you chat (almost) daily many times with and share similar values as yours is always a fantastic feeling. Surprisingly, there were many locals attending the event, also members of the XMPP Berlin Meetup, so I recommend you to check it out if you are near Berlin.

Xavi and I stayed very close to Rummelsburg station, where one can spot an interesting façade.

Rummelsbrug station

I just had to take a picture of it, I don’t usually see buildings that fake they are falling apart :)

The venue was at Deutscher Bundesjugendring (DBJR)’s, the umbrella of German youth organisations.

We had free food and drinks, a luxury I didn’t expect at all! Amazing breakfast with painted eggs It was funny to see painted (and boiled) eggs, because even though I may have painted them as a child at school, it is not something I usually do.

Regarding the drinks, I finally tasted the drink for hackers:

Club-Mate drink

I cannot lie, we had lots of it, and I know now why people choose it for hacking. It sure boosts you up. I have already looked up where to buy it in Copenhagen!

From an organizational point, the event was a complete success, there was enough space for everybody, we had meetings gathering together to report how things were going on (kind of like stand-ups) and of course we did go out for dinner!
We even got a coffee cup courtesy of DBJR, wishing us a safe trip back home. Couldn’t complain at all.

XMPP Berlin Sprint 2019

By Xavi

For the very first time I had the magnific chance to attend an XMPP sprint. It was the first time I visited the beautiful and charming city of Berlin as well so it was a great incentive. However the best experience so far was meeting with so many interesting people focused on improving the ecosystem and helping people to communicate better.

As one of the XEP-0382’s co-authors, the goal on this sprint was to try to implement it on Kaidan so that it can be used and thus we can get some valuable feedback from users, client developers and members of the community in order to improve the XEP in every possible way.

On the objectives part, from here it goes a huge thanks to lnj and jbb for the friendly and fast onboarding on Kaidan, as well as for the guidance and collaboration. Thanks to them it was possible to accomplish the goal within the sprint so I couldn’t be more happy!

The event was a total blast greatly because of Tim, our magnific host that organized the event and took care of everyone, and debacle the ceremony master who took care of the meetings and coordination.

So… last but not least here’s a graphical evidence of the achievement degree on the sprint:

Next steps are to implement support for XEP-0382 in Qxmpp and use the library in Kaidan instead (already in progress)

Abandoned Cart Emails @ Unisport

By Xavi

Originally posted on: Unisport


There are a lot of abandoned cart entries out there from marketing’s perspective, but on this one I’ll also give you an insight on how to develop this kind of feature on your e-commerce.

An abandoned cart is generated when a customer in an e-commerce store adds products to the cart but doesn’t end up buying anything.
In order to help, even persuade, some customers to finally end up buying in this case, there is a widely spread marketing technique: The abandoned cart email.

Let’s put an example:

Alice goes into Unisport, adds a couple of bleeding-edge hi-tech football boots to the cart and starts the checkout process.
At that moment, she receives an important call and leaves the process unfinished.
Luckily enough, after a while she receives an email containing a link to the site letting her know she left some items behind.
Then, she can go to the site again and finally buy her so much desired pair of football boots.

Customer adds items to cart, leaves site, then receives email, goes back to site and finally completes purchase process



First of all let’s take a look at the nature of the feature. Based on marketing studies, there are several factors that affect directly emails’ success. Let’s describe the main ones very briefly:

  • Abandoned cart definition: This is the amount of time that needs to pass from the last cart modification to consider a cart or basket abandoned.

  • Time to reach out customer: This is the amount of time after which you want to reach out the customer since last cart modification.

  • Minimum price of cart: Value total of the items in the cart.

Even though this is not meant to be a marketing lecture, there are some general rules that can work more or less fine out of the box for everyone, and these are:

  • Try to never reach customers more than a couple of days after the cart was abandoned because they might have forgotten about it all along or they are just not interested anymore.

  • Try to reach customers at the same hour of last cart edition because odds are that person has a daily routine and is browsing or available for shopping on the same time frame every day.

  • Avoid reaching out customers for a low total cart value to optimize profitability.

Another crucial aspect to take into account is which checks are needed to be performed before sending out an email for a customer.
Depending on country an explicit consent or subscription to the service may be needed, it might be it’s fine with a general permission throughout the site, some special condition like having previous purchase history or even the case where no permission or consent is needed at all.



Some time ago, at Unisport we realized we didn’t have in place a mechanism to call out for those customers who left an abandoned cart behind so we decided to build our own system to manage the process.

Having sites for more than nine countries, modularity is a must. People around the world have different routines, tempos, interests, laws, shopping behaviors etc… So the “one-size-fits-all” approach, can only be taken advantage of to some extent.

So let’s dive right away into the specifics: In our case, the logic is ran as a task in a worker server periodically i.e. as a cronjob.
When thinking about the scheduling of the task, the bigger the frequency, the more times the task runs per day but the less abandoned carts it has to process on each run and the more accurate time to reach customer is.

Abandoned cart emails sliding window process

As seen in the picture above, there’s another factor that comes into play: How long back in time are abandoned carts fetched. This is defined by the period of time the task covers. Let’s see an example:
Let’s say the task is configured to run every two hours and look up for abandoned carts up to two days ago. In this case, for each run there’s a time window of 48 hours updated every two, or what is the same, on every task run the time window rolls forward two hours. So for each task run, the first two hours of the previous run are excluded and two new hours go in instead.

The task is structured in three distinguished main blocks:

  • Retrieving all abandoned carts

  • Filtering out the ones not fulfilling all required conditions

  • Sending out emails to customers hoping to engage them enough as to get them to finish the process they started.

Abandoned cart emails system overview

STEP 1 - Retrieving all abandoned carts

In the first step, all the main factors of the feature are used to fetch from the database all the carts that have been $abandoned (see description above), were created $time to reach out customer ago and cost at least $minimum price of cart.

Site Active Definition Reach Time Minimum Value True 6h 22h 15 True 3h 24h 28 True 4.5h 20h 25

STEP 2 - Filtering out the ones not fulfilling all required conditions

Here, all the checks come into place filtering out all the carts that don’t match current rules. For each site a batch of carts is evaluated.
Information regarding the checks to perform is loaded dynamically from the configuration.

Site is_subscribed repeat_customer Filter3 True False True True False True False True True False False False

The results of the execution of each batch are stored as well as the configuration used for that specific run. For every filter applied, there’s a value stored showing the result of applying that filter.
Aditionally, other bits of information like if that batch had a customer voucher assigned to it and its value are collected.

Site Batch Filters Results Voucher 1 [ ] [ ] 10 2 [ ] [ ] 0 3 [ ] [ ] 25

This step is more of a fine-grained tuning of the selection in the previous step, yet essential to be able to adapt to different markets, countries or laws.

STEP 3 - Sending out emails to customers

Finally, once the recipients list is set in stone it’s still needed to push the emails out and let them fly away to their future and welcoming owners.
In this stage all the content of the emails is generated based on the site an the content of the cart of that customer.
It’s utterly important to nail this step; amount of emails sent is huge and you don’t want to send by mistake a wrong email or even more than one per cart. Customer won’t enjoy being flooded!



One of the most important topics to have in mind when developing IT projects is monitoring.
Telling whether a condition is too strict and thus is leaving out a lot of customers who could potentially receive an email is something basic, but really hard to guess without proper monitoring in place.
Keeping track of all the steps and every single factor for each task run, provides with a great deal of information for data-driven decision making.

It is highly recommended to use analytics visualization or monitoring tools like Grafana.
They display effectively the status of tasks among others, in real time and in a very easy to understand way. Who can resist to a flashy and colorful chart?!



This was the overview of the abandoned cart process. As you could see this is a feature that needs to be designed taking your very special needs into account.
You may not need too many filters or you may need many more, for example, if you just run a site you will not need certain logic that we apply here.
If you need to implement abandoned cart emails, keep in mind the three steps and you’ll be relishing the success of this project.

Now, if you ever get one of those, you’ll know all it had to happen before you got the email :-)