Turning a 60 € music player into a bit-perfect USB sound card

The SNOWSKY ECHO MINI is a small, cheap, Chinese-made, portable music player built around a Rockchip RKNanoD (Two Cortex-M3 SoC) and two Cirrus Logic CS43131 DACs. It’s an astonishing piece of kit because the DACs are far too good compared to the rest of the components. For the past weeks I have been writing a new firmware for it from scratch. Version 0.98, released today, turns the player into a DAC or USB sound card for the non-audio people. You plug it into a computer or your phone, and the computer’s sound comes out of the player’s headphone jack, sample for sample. Why? Because the audio circuitry and in particular the protection against electrical interference is “poor” in most consumer electronics. Using a DAC powered by its own battery helps to avoid these disturbances, whether they’re caused by your hard drives seeking or a 5,000-watt LED lighting rig with switch-mode power supplies. In my experience, this is far more important for sound quality than a lot of other audiophile ‘voodoo’ gadgets. The problem is that a high-quality DAC from a reputable brand costs at least several hundred euros.

What was supposed to be a simple implementation of an existing function – with the help of Claude Code, a bit of documentation, the SDK and other open-source implementations – turned into 22 test images and a journey down the rabbit hole to understand the device’s clock frequency drifts.

What already existed

Two firmwares already have a “USB DAC” mode:

  • The stock firmware declares a USB Audio Class 1 (UAC1) device: 16 bits, 48 kHz only, an adaptive isochronous endpoint, plus a HID interface so the player’s keys act as media keys on the computer. Fine for YouTube, not so much for a FLAC library at 44.1 kHz that the computer has to resample.
  • EchoPod, another custom firmware for the same player, does UAC1 at 16 or 24 bits, 44.1 or 48 kHz. Its endpoint is adaptive too, and it handles the drift between the computer’s clock and the DACs’ clock by dropping or repeating a sample when its buffer drifts too far. Simple and robust, but not bit-perfect.

Both have clocks that can drift; I’d already noticed some strange behaviour in the SoC clock. We’ll come back to that. But that’s what motivated me to implement a clock feedback.

The plan was:

  1. UAC1 at full speed, 16 and 24 bits, 44.1 and 48 kHz in synchronous mode;
  2. asynchronous mode: the player keeps its own clock and tells the host how many samples to send;
  3. UAC2 at high speed, for the higher sample rates.

USB audio 101

Audio over USB uses isochronous transfers: the host sends one packet per frame (every 1 ms at full speed, or every 125 µs “microframe” at high speed), with no retry. A packet that is missed is gone.

The difficulty is the clock. The host sends, say, 44.1 samples per millisecond on its clock; the DACs consume them on theirs. The two crystals differ by a few tens of ppm, so the buffer between them slowly fills or drains. There are two ways to deal with it:

  • synchronous / adaptive: the device follows the host’s rate (adjusting its own clock, or dropping and repeating samples). The output drifts away from the original signal to make up for the frequency difference, and the clock offset itself is never corrected;
  • asynchronous: the device keeps its own clock and sends the host a feedback value, “send me 44.1017 samples per frame”, on a second endpoint. The host adjusts how many samples it puts in each packet. Nothing is ever dropped or repeated: this is what “bit-perfect” means here.

UAC1 packets at full speed UAC1 at full speed in asynchronous mode: one audio packet and one feedback packet per 1 ms frame.

Step 1: UAC1 at full speed

The USB controller is a Synopsys DWC2, which my firmware already drove for the USB disk mode. I added a generic “class” layer to the driver (descriptors, class requests, an isochronous OUT endpoint), and uac.c for the sound card itself: descriptors, the volume and sample-rate requests, and a copy of each packet straight into the DACs’ DMA ring. No codec involved, so the second core stays asleep.

The DWC2 has a specific quirk with isochronous endpoints: each one is enabled for either an even or odd frame. If a packet arrives in the wrong frame, it is ignored without warning. It is therefore necessary to monitor and reactivate the correct parity after each packet in order to account for the possibility of an offset or an ‘incomplete isochronous OUT’ interruption.

On the device: absolutely no sound for five test images. Linux detected a sound card, packets were coming through, and DMA was working. The cause was two GPIO pins: when the USB controller started up, it configured the pins according to the requirements of USB disk mode. One of them, GPIO2_0, disabled the headphone outputs. During normal playback, this was never noticeable, as the GPIO pins are restored upon exiting USB disk mode.

Second bug: high-pitched beeps whenever the screen changed. At full speed, the endpoint must be re-armed every millisecond, and the main loop was doing it. But drawing one line of text on the screen took 1.4 ms: packets were lost, and each 1 ms hole was filled by repeated samples. The quick fix was to service the USB from inside the drawing routine, between two glyphs.

I played a whole album (29 minutes, 1.7 million packets), not one packet was lost during playback, one drift correction every 50 to 60 seconds. About 40 ppm between my PC’s clock and the Echo Mini. After five or six attempts, I now have a sound card that works in 16 and 24-bit modes at 44.1 and 48 kHz in synchronous mode. The clock shift is inaudible. It’s encouraging, and I reckon I’ll have this sorted out soon (I’d been a bit ill and was short of sleep).

The sound card screen in step 1 Step 1, synchronous mode at 44.1 kHz: the buffer holds 10 ms of audio, and the “repeated” counter shows the samples repeated to make up for the clock drift.

Step 2: asynchronous feedback

The feedback endpoint itself was not difficult. It’s an isochronous IN endpoint on a periodic TX FIFO. The format is a bit strange but well documented: a 3-byte value in 10.14 fixed point (samples per frame), sent every frame.

Computing what to send was the difficult part. My first idea was to measure the DACs’ rate in RX/host frames. The measurements were off by 13 to 33 % and jumped around: the frame number read from the controller skiped values. The value sent to the host hit the ±1 % clock adjustment limit, the buffer drained, and the fallback repeated samples at regular intervals (beeps and static noise).

The sound card screen with the first feedback The first feedback attempt: 43,658.9 Hz is 44,100 Hz minus exactly 1 %, the limit. The host sent too little, and 26,063 samples had to be repeated.

What most asynchronous sound cards do is a control loop on the buffer’s fill level: if the ring holds more than 10 ms of audio, ask for a bit less; if it holds less, ask for a bit more. A proportional-integral controller does it (as a former automation engineer, I’m very fond of PI control). The fill is averaged over about 64 packets. I simulated it in Python for ±40 and +300 ppm before trying it on the device. The integral term ends up holding the clock difference, and the proportional term keeps the buffer on target.

On the device, Linux reported “ASYNC, Feedback Format 10.14”, and over 13 minutes of music: 760,727 packets, zero samples dropped or repeated. The feedback settled at 44,101.7 Hz, which is the same +40 ppm as before, now handled by the host.

There was also a click when entering the mode: the USB start-up toggled those two GPIOs again while the DACs were already running. The fix was to let the sound card keep the audio pins, and to always change the volume through a 3 dB / 2 ms ramp.

Yeah, I now have a sound card that works in 16 and 24-bit modes at 44.1 and 48 kHz in asynchronous mode. It’s not that difficult, it’ll all be fine. I’ll be in bed in an hour!

Step 3a: moving to the USB interrupt

At high speed, a packet can arrive every 125 µs, and no main loop that also draws a screen and polls keys can keep up with that. So before going high speed, the sound card moved to the USB controller’s interrupt (IRQ 5 on this chip): the interrupt handler receives packets, writes them to the ring, re-arms endpoints and sends the feedback. The main loop is left with the screen, the keys, the volume and the log. The drawing hacks from step 1 were removed. Same results as step 2, with about 1,900 interrupts per second, 32 µs at most.

This is a direct improvement for UAC1 mode.

Step 3b: UAC2 at high speed

UAC2 is a different beast from UAC1. There is an interface association descriptor, a clock source entity whose sample rate the host sets, CUR and RANGE requests instead of GET_MIN/GET_MAX/GET_RES, and feedback in 16.16 format, in samples per microframe. I kept UAC1 for full-speed ports and chose the descriptors according to the speed negotiated at enumeration.

Three failed attempts:

  • the feedback keeps the old sample rate after a rate change: the host reads the first feedback packets to guess their format, so the packet already queued had to be replaced at once;
  • Linux ignores a feedback endpoint that does not answer at every polling interval. So no empty or skipped packets;
  • one audio packet every microframe resulted in the loss of half of the packets. Switching to one packet every 4 micro-frames (500 µs) resolved the problem.

UAC2 packets at high speed UAC2 at high speed, as the firmware does it: an audio packet every 4 microframes, the feedback every 8.

The fourth image (97R) worked: six sample rates from 44.1 to 192 kHz, zero samples dropped or repeated, about 6,000 interrupts per second.

…in 16 bits. When I played the same test in 24 bits the next morning, there was no sound at all. Linux showed a perfect feedback rate, but that was only because the device sends the nominal value when it receives nothing. The device’s counters said: zero packets received in four minutes.

The reason is the controller’s receive FIFO. The code was asking for 256 words, but the chip has no dynamic FIFO sizing (one bit in a hardware configuration register says so), so the FIFO stays at 147 words, 588 bytes, whatever you write. A 16-bit packet at 192 kHz (388 bytes) fits. A 24-bit packet in 4-byte slots (776 bytes) does not, and the controller drops it without any error. I added the value read back from the register to the log, and it said “rx fifo 147 words”. It also meant that the “256 words” in my notes from steps 1 and 2 was wrong.

The options were to send a packet every 250 µs (twice the interrupts), 3-byte slots (582 bytes: no room left at all), or stopping at 96 kHz. In UAC2, sample rates belong to the clock, not to the format, so I could not offer 192 kHz in 16 bits only. The controller already generates thousands of interrupts per second; it is possible to push it a little further, but the device becomes very unresponsive and even the slightest interaction can cause frame loss. The 24-bit 176.4 and 192 kHz formats are already quite rare, reserved for archiving audio or masters that are to be processed or mixed. Personally, I don’t have any tracks in this format in my library. I’ve decided to limit the sampling rate to 96 kHz.

So the DAC feature now does 44.1, 48, 88.2 and 96 kHz in 16, 24 or 32 bits, with packets of 392 bytes at most. Result on the device: 167,708 packets, nothing dropped, repeated or re-aligned.

That’s great – we’re offering twice the sampling rate of the original firmware at 24-bit. Just one more test to go and we can release this feature!

The clock bug

The sound card logs the DACs’ real output rate, measured from the DMA’s progress over 10 seconds. During the 97R test, when 176.4 kHz was still offered, it said 175,912 Hz: 0.28 % slow.

The audio PLL settings come from the stock firmware’s tables. My audio player had been using the same ones since its first versions, so 176.4 kHz Audio had always been played 0.28 % slow, a pitch error about four times smaller than what a trained musician can notice. Nobody can hear that, and a test by ear could not catch it either. The sound card feature made it visible, because a feedback loop cannot settle on a rate that is not what it claims.

My first fix was wrong: I took one of the four PLL parameters for a fractional part and “doubled” the 44.1 kHz entry. The device played at 175,200 Hz, with horrible beeps. So I went back to the chip’s reference manual. The PLL’s oscillator runs at 24 MHz × POSDIV / PREDIV, where POSDIV is an 11-bit number split across two registers (8 high bits in one, 3 low bits in the other). Then an output divider follows, coded rather than direct: the stock tables list the expected output frequency next to each entry, which gives the code-to-divider mapping. Yes, it’s horribly complicated just to set a frequency.

Each table entry has four parameters: POSDIV = 8 × the second + the 3 low bits of the first, PREDIV = the third, and the fourth is the output divider code (3 → ÷5, 8 → ÷9, 9 → ÷3, 10 → ÷6). Downstream, the clock unit divides the PLL output by 5 to make the I2S master clock (MCLK), and the I2S block divides MCLK again to get the bit clock, 64 × the sample rate. With that formula, every entry in the original stock tables gives:

POSDIV / PREDIV VCO Output divider PLL output MCLK (÷5) Sample rates (I2S divider) On the device
147 / 25 141.12 MHz ÷5 28.224 MHz 5.6448 MHz 11.025 (÷8), 22.05 kHz (÷4) no clock at all
384 / 50 184.32 MHz ÷9 20.48 MHz 4.096 MHz 8 (÷8), 16 kHz (÷4) exact
192 / 25 184.32 MHz ÷6 30.72 MHz 6.144 MHz 12 (÷8), 24 kHz (÷4) exact
294 / 25 282.24 MHz ÷5 56.448 MHz 11.2896 MHz 44.1 (÷4), 88.2 kHz (÷2) exact
768 / 50 368.64 MHz ÷9 40.96 MHz 8.192 MHz 32 kHz (÷4) exact
384 / 25 368.64 MHz ÷6 61.44 MHz 12.288 MHz 48 kHz (÷4) exact
384 / 25 368.64 MHz ÷3 122.88 MHz 24.576 MHz 96 (÷4), 192 kHz (÷2) exact
588 / 25 564.48 MHz ÷5 112.896 MHz 22.5792 MHz 176.4 kHz (÷2) 0.28 % slow
768 / 25 737.28 MHz ÷9 81.92 MHz 16.384 MHz 64 (÷4), 128 kHz (÷2) far too slow

The bad entries are exactly the ones whose oscillator falls outside 184–369 MHz. The stock parameters are correct on paper; the silicon just does not follow. Months ago I had replaced the 11.025/22.05/64/128 kHz entries with proven PLLs (from TRM documentation and SDK) and different clock ratios without knowing why the stock ones failed. The stock firmware drives the PLL outside the range it can hold, and nobody seems to have checked the accuracy of those entries. The stock firmware does a lot of overclocking, out-of-spec stuff and its frequency adjustment makes no sense; that’s not a surprise.

The fix for 176.4 kHz is to use the 44.1 kHz PLL (282 MHz) and halve the I2S divider. I also added a permanent measurement of the output rate to the player’s log, at each track change. On the player’s test folder, all 15 sample rates from 8 to 192 kHz are now exact, to the 0.015 % precision of a 7-second track.

The table the firmware uses now has only three oscillator frequencies, all inside the proven range. Rates in bold no longer use their stock entry:

VCO PLL output MCLK Sample rates (I2S divider)
184.32 MHz 20.48 MHz 4.096 MHz 8 kHz (÷8), 16 kHz (÷4)
184.32 MHz 30.72 MHz 6.144 MHz 12 kHz (÷8), 24 kHz (÷4)
282.24 MHz 56.448 MHz 11.2896 MHz 11.025 kHz (÷16), 22.05 kHz (÷8), 44.1 kHz (÷4), 88.2 kHz (÷2), 176.4 kHz
368.64 MHz 40.96 MHz 8.192 MHz 32 kHz (÷4), 64 kHz (÷2)
368.64 MHz 61.44 MHz 12.288 MHz 48 kHz (÷4)
368.64 MHz 122.88 MHz 24.576 MHz 96 kHz (÷4), 128 kHz (÷3), 192 kHz (÷2)

One last false alarm

During the final tests I heard a click at every stream start. I suspected the ring, the DAC reconfiguration, the volume ramp… The test tones were sine waves that started abruptly, and an abrupt start clicks on any DAC. With a 50 ms fade-in, the clicks were gone. Make your test signals clean before you blame the device.

Why bother with 0.28 %?

A 0.28 % error is a twentieth of a semitone. No one will hear it on a pop song, so why spend an evening on it?

Because the sound card cannot ignore it. The player and the computer have to agree on the clock to within a few ppm, and 0.28 % is 2,800 ppm. The higher the frequency, the shorter the feedback loop becomes, and the greater the risk of it diverging. It is therefore necessary to achieve a stable sampling rates of 96 kHz.

It’s not just about displaying a high sampling rate; it has to make sense. It is possible to achieve a very high sampling rate by dropping or replaying microframes, or by running at an incorrect and unstable speed. Hence the aim of being ‘bit-perfect’. One hour of music played with a delay of 0.28 per cent lasts ten seconds longer, and a video played in parallel becomes desynchronised in the same way.

Because a PLL outside its range is not a stable clock. The same setting gave no clock at all at 141 MHz and a clock far too slow at 737 MHz. At 564 MHz it was only slightly off, on my unit and at room temperature. Another unit, or a colder morning, could have done worse. A setting that only works “most of the time” is a bug that hasn’t shown up yet.

Finally this small error explained the big ones. Once I understood why 176.4 kHz was off, I understood why 11.025, 22.05, 64 and 128 kHz had failed months earlier, which I had fixed without knowing why. One bug that is understood is worth three that are only patched.

How I tested

Everything was tested on a Linux PC first, without PulseAudio or PipeWire in the way:

  • aplay -D hw:3,0 tone.wav sends the file to the card untouched (no resampling, no mixing);
  • sox -n -r 96000 -b 32 -c 2 tone.wav synth 6 sine 440 vol 0.2 fade t 0.05 6 0.05 makes the tones;
  • /proc/asound/card3/stream0 shows the alternate setting in use, the packet size and, most useful of all, the feedback rate the host is reading (“Momentary freq”);
  • the device itself logs packets received, dropped, repeated and missed, the fill of its ring, the feedback value and the measured output rate, to a text file I read after each session.

The firmware was written mostly by Claude Code. I did the listening tests on the device; the analysis of each log was done with a bunch of python script.

Lessons learn

  • Check what the hardware actually did. Writing 256 to a register does not give you 256 words, so read the value back and log it.
  • Measure, don’t listen. A 0.28 % error is inaudible and was there from the first version. A rate measurement found it in ten seconds.
  • Don’t trust copied parameters. The 176.4 kHz PLL setting came from the stock firmware and had been carried over, unchanged. Now that LLMs make reverse engineering easier, everyone is pulling the same values from the same binary file without too much checking.
  • A failure has a range. Three wrong PLL entries looked like three unrelated bugs; plotted against one number, they were one.
  • When a control loop misbehaves, look at what it controls first. The beeps of step 2 were not a USB problem, they came from a bad estimate of the DACs’ rate.
  • Clean test signals. Half an hour lost to a click that was in the WAV file.

Next steps

  • Some long listening sessions
  • Android and iPhone (and the question of how much current a phone will give)
  • A choice screen when the cable is plugged in,
  • HID media keys.

The full details, in French, are in the project’s study notes, sections 83 to 88, and v0.98 is on GitHub.

GPTchat peut-il vous aider à passer un test python ?

tablettes cunéiformes assyriennes

“Vous rêvez de décrocher votre prochain emploi dans le monde de la technologie, mais vous êtes stressé à l’idée de devoir passer un test de programmation en Python? Ne paniquez pas! Dans cet article, je vous raconte comment j’ai utilisé GPT-3, l’un des modèles de traitement du langage naturel les plus avancés au monde, pour réussir haut la main un test de programmation en Python lors d’un entretien d’embauche. Suivez mes conseils et vous serez prêt à affronter n’importe quel test de programmation en toute confiance.” – ChatGPT

Je perdais mon temps sur Twitter lorsque je suis tombé sur ce tweet qui proposait de passer un test en Python qui serait représentatif de ce qui pourrait être demandé lors d’un entretien d’embauche. L’exercice consistait à implémenter un interpréteur BrainFuck, un langage de programmation étrange et volontairement incompréhensible. L’exercice était interessant, je n’avais jamais codé d’interpreteur j’ai décidé de relever le défi.

Après quelques recherches sur GitHub, j’ai trouvé un interpréteur BF en Python 3 déjà fonctionnel. Je l’ai copié-collé sur la page coderpad.io et j’ai effectué quelques modifications pour utiliser les bons tampons. Lorsque j’ai lancé l’évaluation, j’ai obtenu un score modeste de 6 sur 15. J’ai passé un peu plus de temps sur le problème en fin de journée et j’ai obtenue le 15 sur 15. Fin de l’histoire tout le monde s’en fout. evaluation 15/15

Les choses interessante arrivent après, je poste donc fièrement mon résultat sur Twitter. Parmi les nombreuses réponses au tweet original, l’une d’elles attire mon attention.

“NUL pour un entretien

Déjà chatgpt va le coder à sa place

Ensuite python pour faire des algos de ce type c’est juste pas le bon langage Ensuite tu demandes au dev plusieurs centaines de lignes alors qu’un dev ça code 10 lignes par jour Puis demander un algo étudient à un pro 😑” –@dededede4

Lors de mes précédents tests j’ai pue vérifier qu’il était déjà tout à fait possible de tricher en utilisant des outils comme GitHub Copilot, mais cela nécessite des connaissances en programmation. Mon idée et défi consiste à essayer de produire une solution sans écrire moi-même une seule ligne de code, voire sans apporter aucune modification. Le NoCode est à la mode, c’est l’occasion de tester.

Voici les règles du défi:

  1. Le résultat doit être obtenu à partir d’une seule demande à GPT-3/ChatGPT.
  2. La demande doit être faite en français.
  3. La demande doit être effectuée dans une nouvelle conversation.

Approche naïve

Requette:Je voudrais un interpreteur brainfuck en python 3.

Résultat:

#!/usr/bin/env python3
import sys

def run_bf(bf_code, input_str):
    input_chars = list(input_str)
    input_len = len(input_chars)
    input_index = 0

    # Initialise la mémoire à zéro
    memory = [0]
    pointer = 0

    # Initialise la sortie à une chaîne vide
    output = ""

    # Itère sur chaque caractère du code Brainfuck
    for c in bf_code:
        if c == ">":
            # Incrémente le pointeur de mémoire
            pointer += 1
            if pointer == len(memory):
                memory.append(0)
        elif c == "<":
            # Décrémente le pointeur de mémoire
            pointer -= 1
        elif c == "+":
            # Incrémente la valeur à l'emplacement de mémoire actuel
            memory[pointer] = (memory[pointer] + 1) % 256
        elif c == "-":
            # Décrémente la valeur à l'emplacement de mémoire actuel
            memory[pointer] = (memory[pointer] - 1) % 256
        elif c == ".":
            # Ajoute la valeur de l'emplacement de mémoire actuel à la sortie
            output += chr(memory[pointer])
        elif c == ",":
            # Lit un caractère de l'entrée et le stocke à l'emplacement de mémoire actuel
            if input_index < input_len:
                memory[pointer] = ord(input_chars[input_index])
                input_index += 1
            else:
                memory[pointer] = 0
        elif c == "[":
            # Si la valeur de l'emplacement de mémoire actuel est nulle, saute au caractère de fermeture de la boucle
            if memory[pointer] == 0:
                loop_count = 1
                while loop_count > 0:
                    c = bf_code[i]
                    i += 1
                    if c == "[":
                        loop_count += 1
                    elif c == "]":
                        loop_count -= 1
        elif c == "]":
            # Si la valeur de l'emplacement de mémoire actuel n'est pas nulle, saute au caractère d'ouverture de la boucle
            if memory[pointer] != 0:
                loop_count = 1
                while loop_count > 0:
                    c = bf_code[i]
                    i -= 1
                    if c == "[":
                        loop_count -= 1
                    elif c == "]":
                        loop_count += 1

    return output

def main():
    # Vérifie qu'un fichier a été passé en argument
    if len(sys.argv) != 2:
        print("Utilisation: bf.py fichier.bf")

GPT-3 nous donne un interpréteur BF fonctionnel qui semble avoir été copié-collé depuis un dépôt quelconque, mais c’est un bon début.

30 minutes plutard

Requete: Je voudrai un interpreteur brainfuck en python 3 sous la forme d’une fonction qui prend en entrer le code sous la forme d’un array nomé brainfuck_code et les entrées utilisateurs sous la forme d’un array nomé def solution user_input. La mémoire est cyclique et fait 30000 cellules. Les entrée mémoire sont des bytes non signés.

Résultat:

Voici comment vous pourriez écrire une fonction en Python 3 qui interprète du code Brainfuck. Cette fonction prend en entrée une chaîne de caractères représentant le code Brainfuck et une chaîne de caractères représentant les entrées utilisateur, et retourne une chaîne de caractères représentant la sortie générée par le code Brainfuck:


def interpret_brainfuck(brainfuck_code, user_input):
    # Créer une liste de 30000 bytes non signés pour la mémoire
    memory = [0] * 30000

    # Initialiser les variables d'instruction et de pointeur de mémoire
    instruction_ptr = 0
    memory_ptr = 0

    # Initialiser la sortie à une chaîne vide
    output = ""

    # Initialiser l'index de l'entrée utilisateur à 0
    input_idx = 0

    # Répéter jusqu'à ce que l'instruction pointée soit "EOF" (fin de fichier)
    while brainFuck_code[instruction_ptr] != "EOF":
        # Récupérer l'instruction actuelle
        instruction = brainFuck_code[instruction_ptr]

        # Exécuter l'instruction en fonction de sa valeur
        if instruction == ">":
            # Incrémenter le pointeur de mémoire
            memory_ptr += 1
        elif instruction == "<":
            # Décrémenter le pointeur de mémoire
            memory_ptr -= 1
        elif instruction == "+":
            # Incrémenter la valeur de la cellule de mémoire pointée
            memory[memory_ptr] += 1
        elif instruction == "-":
            # Décrémenter la valeur de la cellule de mémoire pointée
            memory[memory_ptr] -= 1
        elif instruction == ".":
            # Ajouter la valeur de la cellule de mémoire pointée à la sortie
            output += chr(memory[memory_ptr])
        elif instruction == ",":
            # Lire l'entrée utilisateur et affecter sa valeur à la cellule de mémoire pointée
            memory[memory_ptr] = ord(user_input[input_idx])
            input_idx += 1
        elif instruction == "[":
            # Si la valeur de la cellule de mémoire pointée est 0, sauter à la correspondance de "]"
            if memory[memory_ptr] == 0:
                instruction_ptr = brainFuck_code.index("]", instruction_ptr)
        elif instruction == "]":
            # Si la valeur de la cellule de mémoire pointée n'est pas 0, retourner à la correspondance de "["
            if memory[memory_ptr] != 0:
                instruction_ptr = brainFuck_code.index("[", 0, instruction_ptr)

        # Passer à l'instruction suivante
        instruction_ptr += 1

Le code comprend des commentaires en français, ce qui est positif. GPT-3 a également respecté la demande de produire une fonction et de donner des noms de variables appropriés. Cependant, il y a quelques points à améliorer:

  • La fonction ne retourne pas de valeur de sortie.
  • La mémoire n’est pas implémentée de manière cyclique (elle ne retourne pas à zéro une fois qu’elle atteint la fin de la liste).
  • Les entrées ne sont pas traitées comme des entiers, mais comme des chaînes de caractères.”

Après avoir effectué plusieurs tests, j’ai constaté que dans sa version d’essai gratuite, GPT-3 tronquait les réponses qui étaient trop longues lorsque le service était surchargé, ce qui expliquait l’absence de retour de la fonction plutôt qu’un oubli réel de la part du système. On abandonne le code doit être généré en une seul fois.

Requete: la fonction doit retourner la sortie.

Réponse: Pour retourner la sortie générée par le code Brainfuck, il suffit de retourner la chaîne de caractères output à la fin de la fonction interpret_brainFuck. Voici comment la fonction complète pourrait ressembler: …

Requete: La mémoire doit être cyclique.

Réponse: Pour que la mémoire soit cyclique dans l’interpréteur BrainFuck que j’ai proposé, vous pouvez ajouter un modulo 30000 aux opérations d’incrémentation et de décrémentation du pointeur de mémoire. Cela permettra de s’assurer que le pointeur de mémoire reste compris entre 0 et 29999, ce qui correspond aux limites de la mémoire cyclique de 30000 cellules. Voici comment la fonction pourrait être modifiée pour prendre en compte la mémoire cyclique: …

Requete: Les cellules mémoires sont des entiers non signés compris entre 0 et 255.

Pour que les cellules de mémoire soient des entiers non signés compris entre 0 et 255, vous pouvez utiliser le type de données bytes de Python, qui est une séquence immutable d’octets. Vous pouvez créer une liste de 30000 octets non signés en utilisant la fonction bytes de Python et en initialisant chaque élément à 0. Voici comment la fonction interpret_brainFuck pourrait être modifiée pour utiliser des octets non signés pour les cellules de mémoire: …

C’est là que les choses deviennent intéressantes. Le commentaire introductif est plutôt juste, mais le code fourni ne le prend absolument pas en compte. Il recrache le code lors de l’étape précédente.

def interpret_brainFuck(brainFuck_code, user_input):
		...
        if instruction == ">":
            # Incrémenter le pointeur de mémoire de manière cyclique
            memory_ptr = (memory_ptr + 1) % 30000
        elif instruction == "<":
            # Décrémenter le pointeur de mémoire de manière cyclique
            memory_ptr = (memory_ptr - 1) % 30000
        elif instruction == "+":
            # Incrémenter la valeur de la cellule de mémoire pointée
            memory[memory_ptr] += 1
        elif instruction == "-":
            # Décrémenter la valeur de la cellule de mémoire pointée
            memory[memory_ptr] -= 1

Requête: Les valeurs stockées en mémoires sont comprises entre 0 et 255. Réponse:Pour s’assurer que les valeurs stockées en mémoire restent comprises entre 0 et 255, vous pouvez utiliser l’opérateur modulo % dans les opérations d’incrémentation et de décrémentation des cellules de mémoire. Cela permettra de limiter les valeurs des cellules de mémoire à l’intervalle [0, 255]. Voici comment la fonction interpret_brainFuck pourrait être modifiée pour limiter les valeurs des cellules de mémoire à l’intervalle [0, 255]:

def interpret_brainFuck(brainFuck_code, user_input):
		...
        # Exécuter l'instruction en fonction de sa valeur
        if instruction == ">":
            # Incrémenter le pointeur de mémoire de manière cyclique
            memory_ptr = (memory_ptr + 1) % 30000
        elif instruction == "<":
            # Décrémenter le pointeur de mémoire de manière cyclique
            memory_ptr = (memory_ptr - 1) % 30000
        elif instruction == "+":
            # Incrémenter la valeur de la cellule de mémoire pointée de manière modulaire
            memory[memory_ptr] = (memory[memory_ptr] + 1) % 256
        elif instruction == "-":
            # Décrémenter la valeur de la cellule de mémoire pointée de manière modulaire
            memory[memory_ptr] = (memory[memory_ptr] - 1) % 256
        elif instruction == ".":

Au final, j’ai obtenu un code qui fonctionne, mais qui ne passe que 6 tests sur 15. Ce qui n’est pas trop mal si l’on considère que je n’ai écrit aucune ligne de code moi-même. J’ai refait l’exercice en anglais. Cette fois-ci GPT-3 s’en sort beaucoup mieux. Le temps de traitement était plus court, ce qui a permis de réduire les problèmes de troncature des réponses (probablement dus à un délai d’expiration) et il a mieux intégré les remarques. Le code suivant a obtenu un score de 8 sur 15 lors du test initial.

def solution(code, user_input):
    memory = [0] * 30000  # Memory with 30,000 cells, initialized to 0

    # Pointer to current cell in memory
    ptr = 0

    # Index of the current input character
    input_index = 0

    # Output string
    output = ''

    # Loop through each character in the code
    i = 0
    while i < len(code):
        # Get the current character and move to the next
        char = code[i]
        i += 1

        if char == '>':
            # Increment the pointer, wrapping around if necessary
            ptr = (ptr + 1) % len(memory)
        elif char == '<':
            # Decrement the pointer, wrapping around if necessary
            ptr = (ptr - 1) % len(memory)
        elif char == '+':
            # Increment the value at the current cell, wrapping around if necessary
            memory[ptr] = (memory[ptr] + 1) % 256
        elif char == '-':
            # Decrement the value at the current cell, wrapping around if necessary
            memory[ptr] = (memory[ptr] - 1) % 256
        elif char == '.':
            # Append the character with the ASCII value of the current cell to the output string
            output += chr(memory[ptr])
        elif char == ',':
            # Input a character from the user_input list and store its ASCII value in the current cell
            memory[ptr] = ord(user_input[input_index])
            input_index += 1
        elif char == '[':
            # If the value at the current cell is 0, jump to the corresponding ']'
            if memory[ptr] == 0:
                loop = 1
                while loop > 0:
                    char = code[i]
                    i += 1
                    if char == '[':
                        loop += 1
                    elif char == ']':
                        loop -= 1
        elif char == ']':
            # If the value at the current cell is not 0, jump back to the corresponding '['
            loop = 1
            while loop > 0:
                i -= 1
                char = code[i]
                if char == '[':
                    loop -= 1
    print('Output :', output)
    return output

Détecter les IA avec Python 3.10

GPT-3 ne connait que python jusqu’à la version 3.8, si on lui demande d’utiliser la fonction match/case introduite dans python 3.10 il perd les pédales et nous raccompte n’importe quoi.

Q: Pourrais-tu utiliser match case à la place de if elif ?

R: Oui, vous pouvez utiliser un dictionnaire et la fonction get pour remplacer les structures if et elif dans la fonction interpret_brainFuck. Le dictionnaire sera utilisé pour associer chaque instruction BrainFuck à une fonction qui effectue l’opération correspondante. Voici comment la fonction interpret_brainFuck pourrait être modifiée pour utiliser un dictionnaire et la fonction get:

def interpret_brainFuck(brainFuck_code, user_input):
	...
    operations = {
        ">": lambda: setattr(memory_ptr, (memory_ptr + 1) % 30000),
        "<": lambda: setattr(memory_ptr, (memory_ptr - 1) % 30000),
        "+": lambda: setattr(memory[memory_ptr], (memory[memory_ptr] + 1) % 256),
        "-": lambda: setattr(memory[memory_ptr], (memory[memory_ptr] - 1) % 256),
        ".": lambda: setattr(output, output + chr(memory[memory_ptr])),
        ",": lambda: setattr(memory[memory_ptr], ord(user_input[input_idx])),
        "[": lambda: setattr(instruction_ptr, brainFuck_code.index("]", instruction_ptr))
            if memory[memory_ptr] == 0 else None,
        "]": lambda: setattr(instruction_ptr, brainFuck_code.index("[", 0, instruction_ptr))
            if memory[memory_ptr] != 0 else None,
    }

    # Répéter jusqu'à ce que l'instruction pointée soit "EOF" (fin de fichier)
    while brainFuck_code[instruction_ptr] != "EOF":
        # Récupérer l'instruction actuelle et exécuter l'opération correspondante
        operations.get(brainFuck_code[instruction_ptr], lambda: None)()

        # Passer à l'instruction suivante
        instruction_ptr += 1

    # Retourner la sortie générée par le code BrainFuck
    return output

Dans cette version de la fonction, chaque instruction BrainFuck est associée à une fonction lambda qui effectue l’opération correspondante. Lorsque l’instruction est exécutée, la fonction lambda est appelée en utilisant la méthode get du dictionnaire ‘operations’.

Utiliser un dictionnaire de fonctions pourrait être une solution élégante pour résoudre ce problème, mais le code part complètement en vrille et définit des lambda avec des setattr avec deux arguments au lieu de trois. Ensuite, il attend un EOF sur un tableau. Si vous voulez casser GPT-3, demandez-lui de coder en Python 3.10.

Conclusion

En conclusion, est-ce que GPT-3 permet de tricher lors d’un entretien en temps limité et est-ce que le NoCode et les intelligences artificielles vont mettre les développeurs au chômage? La réponse est non. Tout d’abord, dans sa version gratuite, GPT-3 est assez lent à répondre. J’ai mis moins de temps à trouver un dépôt GitHub en utilisant Google qu’à attendre la réponse de GPT-3 lorsque j’ai fait la demande en français. Sans une compréhension fine des problémes les résultats fournis sont inexploitables. Il est difficile et inefficace de lui faire prendre en compte des détails d’implémentation tels que le bouclage mémoire ou certains comportements (débordement d’entier) lorsqu’on l’interroge en anglais, et presque impossible lorsqu’on l’interroge en français. Il sort n’importe quoi dès qu’on lui demande de coder dans un langage qui sort un peu de sa base d’apprentissage. Il pourrait être intéressant de comparer ses performances en fonction des langages et de leur popularité. Les résultats restent tout de même beaucoup plus impressionnants que la majorité de outils “NoCode” qui tiennent plus du légo, programmation visuel.

TL;DR : GPT-3 peut sortir du code identique à une bonne recherche google sur github, il peut commenter le code et le traduire, voir faire quelques modifications mineurs comme les noms de variables. Mais il est très difficile de lui faire faire plus de chose et c’est probablement plus lent que de le faire soit même. Si vous savez coder Github Copilot fait mieux.

Miche Evo Max et Shimano Hollowtech II

Pédalier Miche Pistard 2.0

Voici un court article au sujet des boîtiers de pédalier Miche Evo Max et leur compatibilité avec les boîtiers de pédalier Shimano Hollowtech II. Pourquoi, écrire cette article ? J’ai récément (juillet 2022) acheté un pédalier Miche Pistard 2.0 pour un vélo à pignon fixe. Le fabricant annonce que seul les boîtiers de pédalier de la gamme Miche Evo Max sont compatible avec ce modèle. Cela semble plutôt évident vue qu’ils n’ont que deux gammes de boîtiers, axe carré JIS et Evo Max.

Boîtier Evo Max J’ai donc acheté par la même occasion un boîtier Miche Evo Max au format BSA 68mm (les cadres piste reprennent le standard route). Premières constations le boîtier fait plutôt bas de gamme, les flasques sont en aluminium non anodisé et ne comportent aucune indication de montage. Les indications droite, gauche et le sens de serrage auraient été des miniums.

Les tolérances

Les boîtiers Shimano ou Sram auxquels vous êtes probablement habitué se montent comme dans du beurre. Mon exemplaire de boîtier Miche Evo Max avait des tolérences vraiment mauvaises, ce qui rend le montage pénible. Il y a du jeu d’un côté et c’est serré de l’autre.

L’axe du pédalier est mal guidé dans le boîtier on a donc de fortes chances d’ejecter le cache plastique du roulement gauche, expérience vécue. Même en faisant les choses mal ce genre d’ejection ne m’est jamais arrivé sur un boitier Shimano. Avec tous les frottements, il est difficile de savoir si l’on a atteint le couple de serrage recommandé et si l’on est bien en butté lors du montage. J’ai serré aux 47 Nm recommandé et j’avais encore du jeu axial lors des skids. Ce qui était particulièrement désagréable.

Les montages de roulements sur les boitiers de pédalier

Miche affirme que ses pédaliers ne sont compatibles qu’avec ses propres boîtiers. Cependant, lorsque l’on vérifie les dimensions, on constate que l’axe du pédalier a un diamètre de 24 mm à gauche et à droite, le boîtier standard BSA 68 mm. Ces dimensions correspondent aux dimensions des pédaliers Hollowtech II de Shimano. Il faut toujours se méfier avec les tolérences de montages qui peuvent différés. Mais j’ai donc essayé de monter un boîtier Shimano ULTEGRA SM-BBR60 BSA Hollowtech II sur le pédalier Miche.

Les tolérances de l’axe de pédalier Miche sont soit légèrement différentes de celles d’un pédalier Shimano, soit les tolérences de l’axe et le control qualité chez Miche sont mauvaise (ce qui semble plus probable), car il y avait un peu de frottement lors du montage. J’ai passé le pédalier au congélateur afin de contracter légèrement l’arbre, ce qui a permis de monter le boîtier sans aucun effort ou frottement. Une fois serré au couple recommandé, il n’y avait plus de jeu, plus de bruit et plus la désagrable impression que l’on va perdre une manivelle dés qu’on part un peu de travers.

Conclusion

Avec la fin des pédaliers excellents SRAM Omnium, le pédalier Miche Pistard constitue une bonne alternative à un prix abordable. Les boîtiers de pédaliers Miche sont chers, difficiles à trouver et de mauvaise qualité. Bien que le fabricant affirme le contraire il est tout à fait possible de monter un boîtier Shimano de bien meilleur qualité sur un pédalier Miche.

TL;DR : BB Miche caca, BB Shimano ca passe sur un Miche Pistard 2.0

Uber et la GDPR

Smartphone En ces temps de confinement Covid-19, j’ai enfin prit le temps d’effectuer quelques projet que j’avais dans mes calpins depuis un certain temps. L’un d’eux etait d’effectuer un maximum de demandes d’accès à mes données personnels via la GDPR et d’étudier les données qui m’étaient envoyés. Cela afin d’évaluer la mise en oeuvre de la GDPR par les différentes entreprises et plateformes. De détailler les modalités d’accès, car j’en connais quelque uns d’entre vous pour qui il est plus de facilités a écrire une requête vers une API que d’écrire une lettre manuscrite avec photocopie de pièce d’identité vers la boite postale du service juridique (regard accusateur en direction du système bancaire et les opérateurs télécoms français). Mais aussi de mieux comprendre, le fonctionnement et le modèle économique de ces plateformes. Et enfin, avoir des données réels afin de m’exercer à la visualisation de données de géolocalisation avec Python, Folium et Leaflet.

Le but est de faciliter le travail pour des néophytes et d’encourager les utilisateurs a exercer leurs droits d’accès (un droit que l’on utilise pas c’est un droit qui meure). Il n’est pas prévue de se lancer dans des analyses complexes (reverse d’application mobile ou d’API, dés-anonymisation). Je n’ai malheureusement ni le temps, ni les compétence requise pour le faire.

Dans le premier article, je vais m’attaquer à la plateforme Uber. Ce n’est pas la première analyse que j’ai effectué mais c’est l’une des plus intéressantes et celle qui touche la population la plus importante.

La demande d’accès

On peut demander ses donnée directement depuis le site de Uber via cette page: help.uber.com/riders/... Pour effectuer la demande il faut avoir conservé son compte Uber et le numéro de téléphone utilisé lors de l’enregistrement de l’application mobile. L’authentification sur le site web depuis un nouvel appareil et pour ce type de demande nécessite de l’authentification deux facteurs qui s’effectue par l’envoie d’un code par SMS. Comparativement a mes autres demandes qui nécessite souvent de passer par des échanges de mails avec le responsable GDPR de la société, cela reste plutôt simple. Ma demande a été traité en une trentaine d’heure, ce qui est là aussi plutôt bien. On reçoit un lien par mail qui pointe vers une page de téléchargement. Le lien est valide que 7 jours et la page nécessite une authentifier deux facteurs. Les données sont envoyé sous forme d’une archive ZIP contenant une arborescence et plusieurs fichiers CVS.

En ce qui concerne la demande d’accès, la transmission et sécurité, Uber fait partie des bons élèves.

Les données collectées et transmises

Les données collectées par Uber et transmises dans le cadre des demandes GDPR sont listées là : help.uber.com/riders/.. Ce qui va particulièrement m’intéresse ce sont les données de géolocalisation.

DONNÉES DU PASSAGER Vos données de passager contiennent les informations utilisées pour vous amener à destination, notamment :

  • Les détails des courses tels que les heures et lieux de demande, de début et de fin, ainsi que les distances parcourues ;
  • le prix des courses et la devise utilisée ;
  • 30 jours de données d’événements mobiles, telles que le système d’exploitation de l’appareil, le modèle de l’appareil, la langue de l’appareil, la version de l’application, ainsi que l’heure et le lieu de la collecte des données.

L’arborescence de l’archive est comme suit:

Uber Data/
├── Account and Profile
│   ├── payment_methods.csv
│   └── profile_data.csv
├── readme.html
├── Regional information
│   └── For_California_users.txt
└── Rider
    ├── rider_app_analytics-0.csv
    └── trips_data.csv
  • payment_methods.csv : La liste des cartes et moyen de payment actuels ou passés.
  • profile_data.csv : Correspond au profile utilisateur, on y trouve nos première donnée de géolocation via Signup Lat et Signup Long qui sont les coordonnées lors de l’enregistrement sur l’application. Le champ Referred to Uber? correspond au code de vote éventuel parrain, le champ Referral Code correspond au code de parrainage.
  • readme.html : Une copie de la page listant les données collectés.
  • For_California_users.txt : Un utilisateur peu attentif pourrait ne passer ce fichier texte, mais celui-ci est en réalité très intéressant. Il liste les catégories de partenaires qui peuvent potentiellement avoir accès aux données collectés par Uber et les partenaires avec les quels Uber a échangé vos donnés lors des douze derniers moi. Par exemple dans mon cas:In the past 12 months, Uber has sold your personal data to the following categories of third parties: -Advertising network providers. Uber est plutôt avare de détail au sujet de ses partenaires commerciaux.
  • rider_app_analytics-0.csv : On attaque les choses sérieuses, ce fichier contient les “30 jours de données d’événements mobiles”. C’est une mine d’information et particulièrement d’information de géolocalisation. Le volume d’information est particulièrement étonnant . Fin Mars 2020 après deux semaines de confinement et une utilisation quasi nul de Uber exception faite des manipulation nécessaire à la demande d’accès, le fichier compte plus de 2000 lignes.
  • trips_data.csv : Deuxième fichier particulièrement intéressant, celui-ci contient l’ensemble des “détails des courses tels que les heures et lieux de demande, de début et de fin, ainsi que les distances parcourues”, depuis l’enregistrement sur la plateforme.

rider_app_analytics-0.csv

Comme nous l’avons vue ce fichier comporte toutes les données d’événements mobiles. A titre d’exemple, voici un extrait d’une interaction avec l’application. Lors d’une course à l’autre bout de Paris, suite à une panne sur l’une des ligne de métro j’ai lancé l’application afin d’évaluer l’opportunité de prendre un Uber ou les transports en commun. J’ai finalement pris les transports en commun sans effectuer de réservations. D’après les données d’horodatage, l’opération aura prit 2,966 secondes et aura générée 332 lignes d’événements.

  • Event Time (UTC),Session Start Time (UTC),GPS Time (UTC),Horizontal Accuracy,Latitude,Longitude,Speed (GPS),City,Cellular Carrier,Carrier MCC,Carrier MNC,Google Play Services Status,Device ID,IMEI number,IP Address,Device Language,Device Model,Device OS,Device OS Version,Region,Analytics Event Type
  • 2020-03-07 13:07:53.469 2020-03-07 13:07:49.979 2020-03-07 13:07:53.763 9.5 48.82764 2.33204 0.0 paris Orange F 208 1 success e80b48a6-08c6-4b4a-b457-XXX 10.35.167.XXX 100.69.178.XXX fr SM-A600FN android 9 fr_FR custom

  • … 330 lignes …

  • 2020-03-07 13:07:56.435 2020-03-07 13:07:49.979 2020-03-07 13:07:53.763 9.5 48.82764 2.33204 0.0 paris Orange F 208 1 success e80b48a6-08c6-4b4a-b457-XXX 10.35.167.XXX 100.69.178.XXX fr SM-A600FN android 9 fr_FR custom

Quand l’on dépouille le fichier, il apparaît que chaque ligne correspond à une interaction avec l’appli ou à une réquête de l’API d’Uber. On retrouve trois type d’ “Analytics Event Type”, custom, tap et impression. Chaque déplacement de la carte, zoom, dé-zoom, ou clic est enregistré. le champ GPS Time (UTC) correspond à la date de la dernière position GPS connue. Le champ Horizontal Accuracy correspond à l’écart type sigma de position horizontale. Il est particulièrement utile pour savoir si l’utilisateur est à l’intérieur ou à l’extérieur d’un bâtiment. les champs Latitude,Longitude sont codés en degrés décimaux sur 5 décimales. les champs Carrier MCC, Carrier MNC, correspondent au code Mobile_country_code et Mobile_Network_Codes, 208 01 correspond a Orange France. le champ Device ID correspond au Google Play Services ID for Android le champ IMEI number est vide. C’est peu être un résidu de donnée qui ne sont plus collectés, ou de manière conditionnel (en l’absence de Device ID, lors de l’enregistrement).

En résumé ce fichier nous permet de tracer tout les lieux et dates d’interaction de l’utilisateur avec l’application, même si celui-ci n’effectue aucune réservation. Il nous permet aussi de savoir si il était en intérieur ou à l’extérieur, en déplacement ou statique. Si l’utilisateur utilisent plusieurs terminaux il permet aussi d’identifier les terminaux, les réseaux mobiles utilisés, leurs modèles et la version du système d’exploitation.

Je n’ai malheureusement pas de donnée pour un trajet réel (reservation, prise en charge, trajet, dépose) sur cette période confinement oblique. Je ne suis pas en mesure d’évaluer la fenêtre temporel d’enregistrement de l’application lors d’un trajet. Je n’ai pas observé d’activité dans les journaux lors que l’application n’est pas en premier plan sur le téléphone.

trips_data.csv

C’est le fichier qui contient les journaux de”détails des courses tels que les heures et lieux de demande, de début et de fin, ainsi que les distances parcourues”. Il est de structure assez simple. Voici un exemple d’un aller retour, afin de préserver ma vie privé j’ai choisit un trajet légèrement éloigné de mes lieux de travail et d’habitation.

  • City, Product Type, Trip or Order Status, Request Time, Begin Trip Time, Begin Trip Lat, Begin Trip Lng, Begin Trip Address, Dropoff Time, Dropoff Lat, Dropoff Lng Dropoff Address, Distance (miles), Fare Amount, Fare Currency
  • Cape Town UberX COMPLETED 2019-02-15 19:42:32 +0000 UTC 2019-02-15 19:44:27 +0000 UTC -33.92129 18.41808 105 Bree St, Cape Town City Centre, Cape Town, 8001, Afrique du Sud 2019-02-15 19:51:24 +0000 UTC -33.90604 18.41783 3 Portswood Rd, V & A Waterfront, Cape Town, 8002, Afrique du Sud 1.66 31 ZAR
  • Cape Town UberX COMPLETED 2019-02-15 17:28:51 +0000 UTC 2019-02-15 17:30:03 +0000 UTC -33.9060588 18.417833 3 Portswood Rd, V & A Waterfront, Cape Town, 8002, Afrique du Sud 2019-02-15 17:39:16 +0000 UTC -33.9244897 18.4195529 5 Wale St, Cape Town, 8001, ZA 1.94 40 ZAR

Chaque ligne correspond à un trajet, on retrouve les coordonnées de prise en charge Begin Trip Lat, Begin Trip Lng et de dépose Dropoff Lat, Dropoff Lng. Elles sont codées codés en degrés décimaux sur 5 décimales. Ce fichier est moins verbeux que le précédents, mais nous permet de retracer l’ensemble de l’historique de réservation de l’utilisateur. On peut noter que dans une volonté de nous faciliter l’analyse Uber nous donne les adresses postales des lieux de prise en charge et de dépose, en français. Dans cet exemple on peut facilement identifier que j’effectuais un aller retour d’un hôtel vers un restaurant.

Visualisation des données de géolocalisation

Le code pour visualiser les points d’activité utilisateur et les trajets est disponible là: https://github.com/berettavexee/Uber_map

Capture d'écran 00 Capture d'écran 01

C’est assez simple d’utilisation, il vous suffit de déposer vos fichiers rider_app_analytics-0.csv et trips_data.csv dans le sous repertoire uber et de lancer le script via python3 uber_map.py .

Conclusions

  • Il est plutôt facile d’effectuer la demande d’accès GDPR.
  • Les données fournies sont facilement exploitables.
  • Uber possède un graph des parrainage
  • Uber possède l’historique de toutes les interactions, qui inclue le dernier point de géolocalisation connue sur les 30 derniers jours.
  • Uber conserve les données techniques en mesure d’identifier tous les appareils, réseau téléphonique et réseau IP utilisés sur les 30 derniers jours.
  • Uber conserve un historique complete des trajets effectués.
  • Certaines données fournit par l’utilisateurs comme la notation des chauffeurs ou des trajets n’ont pas été transmises.

remerciements