Magic Mirror

De wikijoan
Salta a la navegació Salta a la cerca
Mm.jpg

Començant a treballar

Projecte oficial:

Mirar també:

El fitxer de configuració important és config/config.js

i el fitxer CSS personalitzat és: css/custom.css

si vull canviar el color del mòdul compliments:

.compliments .bright {
color : #ff0000;
}

és important ficar la classe .bright perquè el div del complimentss deu tenir a dins un div d'aquesta classe.

El bright està definit al main.css


Finalment he aconseguit canviar el tipus de font del mòdul 'compliments':

body {
font-family: "SF";
}

@font-face {
  font-family: "SF";
  font-style: normal;
  font-weight: 100;
  src:
    local("LibreBaskerville-Regular"),
    url("../fonts/LibreBaskerville-Regular.ttf") format("truetype");
}

.clock .time {
  color: yellow;
}

.calendar {
  width: 300px;
}

.compliments .bright {
color : #ff0000;
font-family: SF;
}

on fonts/ està al mateix nivell que css/

Feeds (sindicació de continguts)

He sindicat els continguts del diari ARA. I partint d'aquest mòdul, vull fer que les meves lliçons de català es basin en aquest mòdul.

Els feeds del català tenen aquest format:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<title><![CDATA[El català, cosa de tots]]></title>
<description><![CDATA[El català, cosa de tots]]></description>
<language><![CDATA[ca]]></language>

<item>
<title><![CDATA[Em veig amb cor a llençar-me de cap (<strike>m'atreveixo</strike> a llençar-me de cap)]]></title>
<pubDate><![CDATA[Sat, 09 Mar 2019 20:10:00 +0100]]></pubDate>
</item>

</channel>
</rss>

Google Calendar

Per comprovar que tinc el calendari funcionant:

$ curl -L https://calendar.google.com/calendar/ical/joanqc%40gmail.com/private-82dc357e1e9600d697d3158910546e02/basic.ics

no cal cap api key. He tingut una mica de lio entre events i recordatoris (reminders). Els reminders i les tasques (tasks) no formen part de la API. El meu calendari de 'Joan Quintana' no inclou els reminders i tasks. I és difícil de sincronitzar. Per tant, la conclusió és que no he de fer servir recordatoris, sinó només events.

Mòdul de fotos

Provo amb el mòdul MMM-EasyPix

posem que el tamany vertical siguin 600 px.

aquest mòdul és un lio per tal de ficar múltiples fotos, i al final instal·lo aquest mòdul:

MMM-ImageSlideshow

$ scp foto*.jpg   pi@192.168.1.35:/home/pi/MagicMirror/modules/MMM-ImageSlideshow/exampleImages

Petit canvi en el fitxer MMM-ImageSlideshow.js:

        // if zero do nothing, otherwise set height to a pixel value        
        fixedImageHeight: 500,

Les meves imatges les he fet de 600px d'alt, però en realitat l'altre mòdul que estava provant les estava escalant. En realitat, l'altura correcta de les fotos per tal de què no es solapi amb el div del català és de 500px.

MMM_Webradio i MMM-Buttons

Estava buscant un mòdul per escoltar ràdios per Internet, i aquest és el que em funciona. A més, puc fer play/stop i anar a la següent ràdio station amb botons, mitjançant els pins GPIO.

Per provar els pins GPIO de forma ràpida s'ha de curtcircuitar el pin amb el senyal de 3.3V. Per fer-ho ben fet, amb resistències, com en les màquines Arcade.

Escoltar ràdios:

Amagar i mostrar el cursor

MMM-Cursor (MarinescuEvghenii). Display cursor when user moves mouse and hide after delay.

Funciona molt bé, l'he instal·lat, i ara ja tinc el cursor del mouse sempre que el necessito.

Últims temes per anar acabant

  • Treure el cursor del ratolí. Podrem amagar el cursor del ratolí instal·lant unclutter:
sudo apt-get install unclutter
  • Treure el salva pantalles. Per evitar que es posi la pantalla negra o qualsevol altre estalvi de pantalla, haurem primer instal·lar:
sudo apt-get install x11-xserver-utils
sudo joe /etc/xdg/lxsession/LXDE/autostart

add the following lines:

@xset s noblank
@xset s off
@xset -dpms 


sudo joe /etc/lightdm/lightdm.conf

add the following lines:

xserver-command=X -s 0 -dpms
Reboot the raspberry pi:

sudo apt-get install xscreensaver
sudo apt-get remove xscreensaver
sudo reboot
  • Treure alerta de voltatge

Per treure el raig aquest groc o vagi, el warning de poca txitxa, afegim a '/boot/config.txt’ al final:

avoid_warnings=1

Inicial MagicMirror en l'inici del sistema

He utilitzat PM2 tal com s'explica en aquest enllaç:


en l'inici del sistema protesta (avís modal) perquè no he canviat el password per defecte (forat de seguretat amb SSH). Canvio el password (amb raspi-config) per S*******.

Comandes útils per apagar, reiniciar i veure els logs del Magic Mirror:

$ pm2 restart mm
$ pm2 start mm
$ pm2 stop mm
$ pm2 log mm

Això pot ser útil si vull apagar/arrencar el MM des d'un script bash.

Mòduls per mirar

  • MMM-Navigate AxLED A module to connect a rotary encoder to MagicMirror and use it for Navigation inside of MagicMirror.

Monitor

  • Monitor AOC LM727

specs: 1280 × 1024 @75Hz. té una sortida VGA (analògica)

Recordem:

hdmi_group=1
hdmi_mode=36

# uncomment for composite PAL
sdtv_mode=2
sdtv_aspect=1

(TBD, encara no funciona)

Targeta de so USB

Vaig comprar fa temps una targeta de so USB, que incorpora dues sortides jack: output i input (speakers i micròfon). Això és important perquè la RPi3 no incorpora microfon.

$ cat /proc/asound/cards
 0 [PCH            ]: HDA-Intel - HDA Intel PCH
                      HDA Intel PCH at 0xc2600000 irq 29
 1 [Device         ]: USB-Audio - USB PnP Sound Device
                      USB PnP Sound Device at usb-0000:00:1d.0-1.1, full speed
$ lsusb

Bus 001 Device 006: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller

 0 [ALSA           ]: bcm2835_alsa - bcm2835 ALSA
                      bcm2835 ALSA
 1 [Device         ]: USB-Audio - USB PnP Sound Device
                      USB PnP Sound Device at usb-3f980000.usb-1.1.3, full speed
$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Ajusto el volum:

$ alsamixer -c 1

alsamixer és l'eina gràfica (de consola) de amixer. El dispositiu c=1 té output i input, que per defecte estan a mínims, i els poso cap al 80%. I ara ja puc fer les proves, que funcionen amb una qualitat més que acceptable:

$ speaker-test -c2 -D plughw:1 -twav
$ arecord -f cd -t wav -D plughw:1 foobar.wav
$ aplay -v -D plughw:1 foobar.wav

Ara que ja tinc un microfon que funciona, em puc plantejar instal·lar algun mòdul que necessiti el micròfon. Com a sortida puc fer servir la sortida estàndar (device=0) o la sortida per la targeta USB (device=1).

Voice related modules

Aquest és un tema complicat. M'he estat barallant amb JASPER, pocketsphinx, kalliope, etc (TBD)

NOTA: els mòduls que depenen de snowboy (snowboy i voicecontrol) quedaran discontinuats el Des 2020.

Hi hauria dues aproximacions: dependre de serveis al núvol (com Alexa o Google Voice), o bé tenir-ho tot a la RPi (sphinx, pocketsphins).

Instal·lar i configurar pocketsphinx seria el millor i tenir-ho tot en local. He aconseguit bons resultats de reconeixement del meu diccionari d'anglès, però no ho he aconseguit en castellà i català. Crec que sí que es pot aconseguir.

Concretament m'interessa:

perquè depèn de pocketsphinx que ja ho tinc quasi funcionant.

Instal·lació sphinxbase i pocketsphinx

MMM-Voice

Compte! MMM-Voice té la part de STT i la integració amb Magic Mirror, però no té la part de TTS.

If you can live with latency, bugged detections and want to have data privacy, feel free to use this module.

La latència seria el problema principal. Tinc resultats de 3 segons d'espera. D'altra banda, he d'estudiar quins resultats tinc en condicions de soroll. L'avantatge és que com que depèn de pocketsphinx, com més conegui aquesta tecnologia més millores puc tenir, i es pot configurar per català, per castellà, i per les condicions de soroll que seran habituals, entrenant de forma adequada el sistema. Efectivament, és sensible al soroll.

$ git clone https://github.com/fewieden/MMM-voice.git
cd MMM-voice/installers 
bash dependencies.sh

Allò bo del cas és que diu que This will need a couple of minutes, quan de fet està instal·lant sphinxbase i pocketsphinx, que jo ja els tenia instal·lats. Efectivament, està compilant un altre vegada el projecte, o sigui que de couple of minutes res de res.

...
[STEP 2/5] Installing sphinxbase | Done
...
[STEP 3/5] Installing pocketsphinx | Done
[STEP 4/5] Exporting paths
[STEP 4/5] Exporting paths |  Done
[STEP 5/5] Installing npm dependencies
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated browserslist@1.7.7: Browserslist 2 could fail on reading Browserslist >3.0 config used in other tools.
npm WARN deprecated circular-json@0.3.3: CircularJSON is in maintenance only, flatted is its successor.
npm notice created a lockfile as package-lock.json. You should commit this file.
added 449 packages from 484 contributors and audited 449 packages in 249.493s

21 packages are looking for funding
  run `npm fund` for details

found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details
[STEP 5/5] Installing npm dependencies | Done
[INFO] Possible Audio Devices to set in config.js

 0 [ALSA           ]: bcm2835_alsa - bcm2835 ALSA
                      bcm2835 ALSA
 1 [Device         ]: USB-Audio - USB PnP Sound Device
                      USB PnP Sound Device at usb-3f980000.usb-1.1.3, full s

sphinxbase i pocketsphinx han quedat instal·lats a:

  • /home/pi/sphinxbase
  • /home/pi/pocketsphinx

i jo tenia una instal·lació prèvia que encara funciona a: (però el projecte és el mateix)

  • /home/pi/sphinxbase-5prealpha
  • /home/pi/pocketsphinx-5prealpha

Però, a més de compilar els dos projectes C++, també hi ha dins la carpeta node_modules el mòdul de python pocketsphinx, que deu cridar al projecte C++.

Explicació important:

{
    module: 'MMM-voice',
    position: 'bottom_bar',
    config: {
        microphone: 1,
		keyword: 'MAGIC MIRROR',
		timeout: 15,
		debug:false
    }
}

NOTA: posar debug:true per veure els errors. Concretament, tinc l'error:

ERROR: "acmod.c", line 76: Acoustic model definition is not specified either with -mdef option or with -hmm

que ja sé de què va.

en el config.js, en el mòdul, posar

debug:true

i ara ja puc mirar el debug.log

INFO: feat.c(715): Initializing feature stream to type: '1s_c_d_dd', ceplen=13, CMN='live', VARNORM='no', AGC='none'
ERROR: "acmod.c", line 76: Acoustic model definition is not specified either with -mdef option or with -hmm
ERROR: 'modules/MMM-voice' does not contain acoustic model definition 'mdef'

    this._psc = spawn('pocketsphinx_continuous', [
      '-adcdev',
      mic,
      '-inmic',
      'yes',
      '-lm',
      `modules/${this.setId}/${this.setId}.lm`,
      '-dict',
      `modules/${this.setId}/${this.setId}.dic`
    ]);

    this._psc = spawn('pocketsphinx_continuous', [
      '-adcdev',
      'plughw:1,0',
      '-inmic',
      'yes',
      '-samprate',
      16000,
      '-nfft',
      2048,
      '-lm',
      `modules/${this.setId}/${this.setId}.lm`,
      '-dict',
      `modules/${this.setId}/${this.setId}.dic`,
      '-hmm',
      `/home/pi/cmusphinx-en-us-ptm-8khz-5.2`
    ]);



Error opening audio device plughw:1,0 for capture: Connection refused
FATAL: "continuous.c", line 245: Failed to open audio device

No sé perquè no s'obre el dispostiu, perquè tinc comprovat que funciona.

MMM-voice: solució dels problemes

en el config.js, posar debug:true

{
    module: 'MMM-voice',
    position: 'bottom_bar',
    config: {
        microphone: 1,
		keyword: 'MAGIC MIRROR',
		timeout: 15,
		debug:false
    }
}

i ara ja puc mirar el debug.log:

$ cat /home/pi/MagicMirror/modules/MMM-voice/debug.log

Ara dóna aquest problema:

INFO: feat.c(715): Initializing feature stream to type: '1s_c_d_dd', ceplen=13, CMN='live', VARNORM='no', AGC='none'
ERROR: "acmod.c", line 76: Acoustic model definition is not specified either with -mdef option or with -hmm
ERROR: 'modules/MMM-voice' does not contain acoustic model definition 'mdef'

Això ja sé de què va i adjunto una carpeta que inclou el fitxer mdef (el que no entenc és com el projecte funciona out-of-the-vox):

    this._psc = spawn('pocketsphinx_continuous', [
      '-adcdev',
      'plughw:1,0',
      '-inmic',
      'yes',
      '-samprate',
      16000,
      '-nfft',
      2048,
      '-lm',
      `modules/${this.setId}/${this.setId}.lm`,
      '-dict',
      `modules/${this.setId}/${this.setId}.dic`,
      '-hmm',
      `/home/pi/cmusphinx-en-us-ptm-8khz-5.2`
    ]);

I ara dóna un altre error, que és el que m'ha donat més mal de caps:

Error opening audio device plughw:1,0 for capture: Connection refused
FATAL: "continuous.c", line 245: Failed to open audio device

This particular error means you compiled with pulseaudio but you do not have pulseaudio daemon running.

Aquí m'he estat molta estona intentant fer funcionar MMM-voice només amb ALSA, sense pulseaudio, i val a dir que no ho he aconseguit, que al final instal·lo pulseaudio.

La qüestió és que el pocketsphinx_continuous no necessita per a res el pulseaudio:

$ whereis pocketsphinx_continuous 
pocketsphinx_continuous: /usr/bin/pocketsphinx_continuous /usr/local/bin/pocketsphinx_continuous /usr/share/man/man1/pocketsphinx_continuous.1.gz

cd ~/pocketsphinx/src
sudo make install

pocketsphinx_continuous -samprate 16000 -adcdev plughw:1,0 -inmic yes -nfft 2048 -dict /home/pi/MagicMirror/modules/MMM-voice/MMM-voice.dic -hmm /home/pi/cmusphinx-en-us-ptm-8khz-5.2 -lm /home/pi/MagicMirror/modules/MMM-voice/MMM-voice.lm 2>./unwanted-stuff.log | tee ./words.log

i funciona bé, puc provar per 'MAGIC MIRROR'

però quan ho faig a través del MagicMirror, el debug.log em diu que:

Error opening audio device plughw:1,0 for capture: Connection refused
FATAL: "continuous.c", line 245: Failed to open audio device

El problema no és del pocketsphinx, sinó que deu ser del MagicMirror (MMM-voice), que necessita pulseaudio.


Efectivament, en la meva RPi no tenia el pulseaudio, i suposo que la compilació de sphinxbase va fallar i no me'n vaig enterar. Doncs no, la compilació no falla, senzillament s'instal·la sphinxbase sense pulseaudio perquè no hauria de ser necessari, amb ALSA n'hi hauria d'haver prou.

projecte /home/pi/sphinxbase:

./autgogen.sh

checking pulse/pulseaudio.h usability... no
checking pulse/pulseaudio.h presence... no
checking for pulse/pulseaudio.h... no
checking alsa/asoundlib.h usability... yes
checking alsa/asoundlib.h presence... yes
checking for alsa/asoundlib.h... yes
checking for snd_pcm_open in -lasound... yes

no hi ha instal·lat el pulseaudio, però això no hauria de ser un problema, perquè aleshores s'hauria de fer servir ALSA directament.

$ make
$ sudo make install

Compila bé sense pulseaudio. Torno a fer proves, i no hi ha manera, hauré d'instal·lar el pulseaudio, encara que no volia.

libpulse-dev - PulseAudio client development headers and libraries
pulseaudio - PulseAudio sound server
pulseaudio-utils - Command line tools for the PulseAudio sound server

$ sudo apt-get install pulseaudio libpulse-dev pulseaudio-utils
Check if any pulseaudio instance is running:
pulseaudio --check
It normally prints no output, just exit code. 0 means running. Mine were not running, so I just advanced to step 3.

If any instance is running:
pulseaudio -k

Finally, start pulseaudio again as a daemon:
pulseaudio -D

Ara que tinc pulseaudio, torno a compilar sphinxbase: (no fos cas que ara funcioni)

$ .autogen.sh

...
checking pulse/pulseaudio.h usability... yes
checking pulse/pulseaudio.h presence... yes
checking for pulse/pulseaudio.h... yes

make clean
make
sudo make instll

I torne-m'hi (però compte! que l'error és diferent):

Error opening audio device plughw:1,0 for capture: Invalid argument
FATAL: "continuous.c", line 245: Failed to open audio device

Continua sense obrir el audio device, però ara l'error és diferent: 'invalid argument', no li agrada el plughw:1,0


Finalment (solució):

    this._psc = spawn('pocketsphinx_continuous', [
      '-adcdev',
      1,

$ cat /home/pi/MagicMirror/modules/MMM-voice/debug.log
...
INFO: continuous.c(252): Ready....

Lo bo del cas és que si ara mato pulseaudio, també funciona.

pulseaudio -k

perquè el MagicMirror arrenca el pulseaudio. Suposo que la manera que vol funcionar el MagicMirror és: 1) troba el pulseaudio i està apagat: l'arrenca; 2) no troba pulseaudio: continua amb alsa i deu afegir 'plughw:' al nom de la targeta de so. A mi m'hagués agradat no haver d'instal·lar pulseaudio, però no hi ha hagut manera.

Bé, ara que funciona tornem a deixar les coses al seu lloc.

MMM-voice/node_modules/pocketsphinx-continuous/index.js:

    this._psc = spawn('pocketsphinx_continuous', [
      '-adcdev',
      1, -> està hardcoded ****************
      '-inmic',
      'yes',
      '-samprate',
      16000,
      '-nfft',
      2048,
      '-lm',
      `modules/${this.setId}/${this.setId}.lm`,
      '-dict',
      `modules/${this.setId}/${this.setId}.dic`,
      '-hmm',
      `/home/pi/cmusphinx-en-us-ptm-8khz-5.2`
    ]);

MagicMirror/confi/config.js:

{
    module: 'MMM-voice',
    position: 'bottom_bar',
    config: {
        microphone: 'plughw:1,0', -> tant se val el que posa aquí, perquè ho tindré hard-coded
        keyword: 'MAGIC MIRROR',
        timeout: 15,
        debug:false
    }
},

El problema (un dels problemes) és que en el codi intenta afegir el plughw si és que no li he ficat ja. Però el cas és que el que espera el paràmetre -adcdev és un 1 tal qual.

MMM-voice: configuració de les paraules i funcionament

For the sake of simplicity, I have used the so-called [lmtool](http://www.speech.cs.cmu.edu/tools/lmtool-new.html) to generate a dictionary and language model. This will give you a download consisting of a couple of files, and they are usually named in a specific way. For example if your submission generated a "set id" of 1337, then the files inside the downloaded archive would be named (among other) 1337.lm and 1337.dic. These are the ones we use. So if you have downloaded a different dictionary and/or language model, you can rename them to an arbitrary string (preferrably both of them the same) and use that as the `setId` config parameter.

When you use this module, make sure these files are in the working directory.

aquesta eina ja l'havia fet servir ahir. Per tant, la puc tornar a fer servir per afegir nou vocabulari i noves comandes. Ara bé, això només és per a l'anglès.

Protocol per fer canvis en el diccionari

en el portàtil
sshpass -p "********" scp TAR3511.tgz pi@192.168.1.60:/home/pi/MagicMirror/modules/MMM-voice

en la RPI3:
cd /home/pi/MagicMirror/modules/MMM-voice/
tar xvzf TAR3511.tgz
mv 3511.dic MMM-voice.dic
mv 3511.lm MMM-voice.lm
rm 3511.*
pm2 restart mm

Millores:

  • estic amb l'anglès. No hi ha manera de pronunciar of (show standings of spain). Com que només interessa spain, només s'haurà de dir show standings o show classification).

MMM-voice: idioma català

(TBD)

JASPER (NO)

Instal·lació

M'ha donat molts problemes, i m'he quedat encallat només amb la instal·lació de pocketsphinx. Lo bo del cas és que se m'instal·la bé des de les fonts.

Mòduls de Jasper

mòdul de jasper per reproduir fitxers wav

  • github.com/ArtBIT/jasper-module-wavplay

Mòdul de la viquipèdia

Es tracta d'extreure l'extracte d'un contingut de la viquipèdia. L'extracte és el text que tenim al principi del tot, abans del TOC. Es pot adaptar a la viquipèdia catalana, hauria de funcionar (TBD).

Jasper en català

Canviar el nom de Jasper

Can I change Jasper's name?

Sure, you can just replace the Jasper name in main.py and musicmode.py. Then, create a new dictionary_persona.dic and languagemodel_persona.lm to include the new name you decide. We recommend the online lmtool to regenerate the dictionary and language model. More information is available here:

Kalliope (NO)

M'he mirat 'kalliope perquè no hi ha hagut manera de fer funcionar el pocketsphinx de Jasper:

But as Milliways said this link is obsolete and Jasper seems not under active development anymore (last commit was from January 2017)

You may want to use an alternative like Kalliope. It aims to be a personal assistant with a similar concept like Alexa Skills.

Instal·lació:

$ bash -c "$(curl -sL https://raw.githubusercontent.com/kalliope-project/kalliope/master/install/rpi_install_kalliope.sh)"

s'ha instal·lat bé, queda instal·lat a /usr/local/bin/kalliope

$ /usr/local/bin/kalliope start
Starting Kalliope
Press Ctrl+C for stopping
Starting order signal
je suis prête
...
Waiting for trigger detection

si no tinc insertada la targeta de so (el micròfon) dóna l'error de no input device detected.

Kalliope needs two files to works, a settings.yml and a brain.yml.

Els tinc aquí:

$ ls kalliope/kalliope
brain.yml  __init__.py  players       signals  trigger  _version.py
core       neurons      settings.yml  stt      tts

$ /usr/local/bin/kalliope --help
usage: kalliope [-h] [--run-synapse RUN_SYNAPSE] [--run-order RUN_ORDER]
                [--brain-file BRAIN_FILE] [--debug] [--git-url GIT_URL]
                [--neuron-name NEURON_NAME] [--stt-name STT_NAME]
                [--tts-name TTS_NAME] [--trigger-name TRIGGER_NAME]
                [--signal-name SIGNAL_NAME] [--deaf] [-v]
                action

Kalliope

positional arguments:
  action                [start|install|uninstall]

optional arguments:
  -h, --help            show this help message and exit
  --run-synapse RUN_SYNAPSE
                        Name of a synapse to load surrounded by quote
  --run-order RUN_ORDER
                        order surrounded by a quote
  --brain-file BRAIN_FILE
                        Full path of a brain file
  --debug               Show debug output
  --git-url GIT_URL     Git URL of the neuron to install
  --neuron-name NEURON_NAME
                        Neuron name to uninstall
  --stt-name STT_NAME   STT name to uninstall
  --tts-name TTS_NAME   TTS name to uninstall
  --trigger-name TRIGGER_NAME
                        Trigger name to uninstall
  --signal-name SIGNAL_NAME
                        Signal name to uninstall
  --deaf                Starts Kalliope deaf
  -v, --version         show program's version number and exit
  ---

kalliope és una bona idea, incorpora TTS i STT, però com es veu en el fitxer kalliope/kalliope/settings.yml, per defecte depèn de snowboy, que es descontinua el desembre de 2020, i el speech_to_text de google (que és del núvol). Segurament és modular i es pot canviar els serveis TTS i STT a serveis en local.

triggers:
  - snowboy:
      pmdl_file: "trigger/snowboy/resources/kalliope-FR-117samples.pmdl"


# ---------------------------
# Speech to text
# ---------------------------

# This is the STT that will be used by default
default_speech_to_text: "google"

Mòdul MMM-soccer (exemple de MMM-voice)

Aquest mòdul suporta MMM-voice i el puc agafar com a exemple per aprendre vàries coses:

  1. com s'integra i com funciona un mòdul que fa servir MMM-voice
  2. com puc fer sonar amb Festival un text i integrar-ho en qualsevol mòdul. Per exemple, en aquest cas, dir: 'està guanyant el Barça amb 65 punts'.
  3. com funciona programàticament que s'amaguin i apareixin els mòduls. El resultat de la lliga ha d'aparèixer en l'espai del mig, que és l'espai que ocupa per exemple les fotos. Jo vull que quan accedeixi al mòdul de MMM-soccer em desapareguin les fotos i l'espai del mig l'ha d'ocupar el resultat de la lliga.

API de football-data.org

La versió 1 de la API ja no funciona, estan en la v2. Per tant el codi tal qual segur que no funciona, s'haurà de modificar.

Your API email: joanqc@gmail.com
Your API token: *******
Your API plan: Free Tier

curl

Classificació de l'actual temporada de la lliga espanyola:

$ curl -X GET -i  -H "X-Auth-Token: *******" -H "Accept: application/json" api.football-data.org/v2/competitions/PD/standings/

python

el mateix amb python:

import http.client
import json

connection = http.client.HTTPConnection('api.football-data.org')
headers = { 'X-Auth-Token': '*******' }
connection.request('GET', '/v2/competitions/PD/standings/', None, headers )
response = json.loads(connection.getresponse().read().decode())

print (response)

{'filters': {}, 'competition': {'id': 2014, 'area': {'id': 2224, 'name': 'Spain'}, 'name': 'Primera Division', 
'code': 'PD', 'plan': 'TIER_ONE', 'lastUpdated': '2020-05-16T00:00:10Z'}, 
'season': {'id': 519, 'startDate': '2019-08-16', 'endDate': '2020-05-24', 'currentMatchday': 38, 'winner': None}, 
'standings': 
	[
	{'stage': 'REGULAR_SEASON', 'type': 'TOTAL', 'group': None, 
	'table': 
	[
		{'position': 1, 'team': {'id': 81, 'name': 'FC Barcelona', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/a/aa/Fc_barcelona.svg'}, 'playedGames': 27, 'won': 18, 'draw': 4, 'lost': 5, 'points': 58, 'goalsFor': 63, 'goalsAgainst': 31, 'goalDifference': 32},
		{'position': 2, 'team': {'id': 86, 'name': 'Real Madrid CF', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/3/3f/Real_Madrid_Logo.svg'}, 'playedGames': 27, 'won': 16, 'draw': 8, 'lost': 3, 'points': 56, 'goalsFor': 49, 'goalsAgainst': 19, 'goalDifference': 30}, 
		{'position': 3, 'team': {'id': 559, 'name': 'Sevilla FC', 'crestUrl': 'https://upload.wikimedia.org/wikipedia/de/c/c0/FC_Sevilla.svg'}, 'playedGames': 27, 'won': 13, 'draw': 8, 'lost': 6, 'points': 47, 'goalsFor': 39, 'goalsAgainst': 29, 'goalDifference': 10}, 
		{'position': 4, 'team': {'id': 92, 'name': 'Real Sociedad de Fútbol', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/5/55/Real_Sociedad_San_Sebasti%C3%A1n.svg'}, 'playedGames': 27, 'won': 14, 'draw': 4, 'lost': 9, 'points': 46, 'goalsFor': 45, 'goalsAgainst': 33, 'goalDifference': 12}, 
		{'position': 5, 'team': {'id': 82, 'name': 'Getafe CF', 'crestUrl': 'https://upload.wikimedia.org/wikipedia/en/7/7f/Getafe_logo.png'}, 'playedGames': 27, 'won': 13, 'draw': 7, 'lost': 7, 'points': 46, 'goalsFor': 37, 'goalsAgainst': 25, 'goalDifference': 12}, 
		{'position': 6, 'team': {'id': 78, 'name': 'Club Atlético de Madrid', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/c/c1/Atletico_Madrid_logo.svg'}, 'playedGames': 27, 'won': 11, 'draw': 12, 'lost': 4, 'points': 45, 'goalsFor': 31, 'goalsAgainst': 21, 'goalDifference': 10}, 
		{'position': 7, 'team': {'id': 95, 'name': 'Valencia CF', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/7/75/FC_Valencia.svg'}, 'playedGames': 27, 'won': 11, 'draw': 9, 'lost': 7, 'points': 42, 'goalsFor': 38, 'goalsAgainst': 39, 'goalDifference': -1}, 
		{'position': 8, 'team': {'id': 94, 'name': 'Villarreal CF', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/7/70/Villarreal_CF_logo.svg'}, 'playedGames': 27, 'won': 11, 'draw': 5, 'lost': 11, 'points': 38, 'goalsFor': 44, 'goalsAgainst': 38, 'goalDifference': 6}, 
		{'position': 9, 'team': {'id': 83, 'name': 'Granada CF', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/d/d3/Granada_CF.svg'}, 'playedGames': 27, 'won': 11, 'draw': 5, 'lost': 11, 'points': 38, 'goalsFor': 33, 'goalsAgainst': 32, 'goalDifference': 1}, 
		{'position': 10, 'team': {'id': 77, 'name': 'Athletic Club', 'crestUrl': 'https://upload.wikimedia.org/wikipedia/en/9/98/Club_Athletic_Bilbao_logo.svg'}, 'playedGames': 27, 'won': 9, 'draw': 10, 'lost': 8, 'points': 37, 'goalsFor': 29, 'goalsAgainst': 23, 'goalDifference': 6}, 
		{'position': 11, 'team': {'id': 79, 'name': 'CA Osasuna', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/c/ca/Atletico_Osasuna.svg'}, 'playedGames': 27, 'won': 8, 'draw': 10, 'lost': 9, 'points': 34, 'goalsFor': 34, 'goalsAgainst': 38, 'goalDifference': -4}, 
		...
		{'position': 20, 'team': {'id': 80, 'name': 'RCD Espanyol de Barcelona', 'crestUrl': 'http://upload.wikimedia.org/wikipedia/de/a/a7/RCD_Espanyol_De_Barcelona.svg'}, 'playedGames': 27, 'won': 4, 'draw': 8, 'lost': 15, 'points': 20, 'goalsFor': 23, 'goalsAgainst': 46, 'goalDifference': -23}
	]
	},
	...

nodejs

I ara el codi mínim amb nodejs (fetch):

script lliga_espanyola.js

$ npm install node-fetch --save
const fetch = require('node-fetch')
var server = 'https://api.football-data.org/v2/competitions/PD/standings/'

var headers = {
  "X-Auth-Token": "********"
}

fetch(server, { method: 'GET', headers: headers})
.then((res) => {
    //console.log(res)
    return res.json()
})
.then((json) => {
    //console.log(json)

    for (let i=0; i<json.standings[0].table.length; i++) {
      let position = json.standings[0].table[i].position
      let team = json.standings[0].table[i].team.name
      let points = json.standings[0].table[i].points
      console.log(position + ". " + team + " (" + points + ")")
    }
})
$ nodejs lliga_espanyola.js

1. FC Barcelona (58)
2. Real Madrid CF (56)
3. Sevilla FC (47)
4. Real Sociedad de Fútbol (46)
5. Getafe CF (46)
6. Club Atlético de Madrid (45)
7. Valencia CF (42)
8. Villarreal CF (38)
9. Granada CF (38)
10. Athletic Club (37)
11. CA Osasuna (34)
12. Real Betis Balompié (33)
13. Levante UD (33)
14. Deportivo Alavés (32)
15. Real Valladolid CF (29)
16. SD Eibar (27)
17. RC Celta de Vigo (26)
18. RCD Mallorca (25)
19. CD Leganés (23)
20. RCD Espanyol de Barcelona (20)

Partits de la jornada

var server = 'https://api.football-data.org/v2/matches?competitions=PD&dateFrom=2020-02-15&dateTo=2020-02-16'

script partits_jornada.js:

const fetch = require('node-fetch')
var server = 'https://api.football-data.org/v2/matches?competitions=PD&dateFrom=2020-05-16&dateTo=2020-05-17'

var headers = {
  "X-Auth-Token": "*******"
}

fetch(server, { method: 'GET', headers: headers})
.then((res) => {
    //console.log(res)
    return res.json()
})
.then((json) => {
    //console.log(json)
    let goalsHome, goalAway
    for (let i=0; i<json.matches.length; i++) {
      if (json.matches[i].score.fullTime.homeTeam !== null) {
        goalsHome = json.matches[i].score.fullTime.homeTeam
        goalsAway = json.matches[i].score.fullTime.awayTeam
      } else {
        goalsHome = ""
        goalsAway = ""
      }
      console.log(json.matches[i].homeTeam.name + " - " + json.matches[i].awayTeam.name + ": " + goalsHome + "-" + goalsAway)
    }
})

$ nodejs partits_jornada.js

RCD Mallorca - Deportivo Alavés: 1-0
FC Barcelona - Getafe CF: 2-1
Villarreal CF - Levante UD: 2-1
Granada CF - Real Valladolid CF: 2-1
Sevilla FC - RCD Espanyol de Barcelona: 2-2
CD Leganés - Real Betis Balompié: 0-0
Athletic Club - CA Osasuna: 0-1
Real Madrid CF - RC Celta de Vigo: 2-2

script partits_jornada_v2.js: (mira la data, i diu els resultats de la jornada actual, si estem a dissabte o diumenge, o de la última jornada)

const fetch = require('node-fetch')

//he d'esbrinar el dissabte i diumenge anteriors a la data actual (i si estem a dissabte, avui i demà:
var dia_actual = new Date();
//console.log(dia_actual)
//console.log(dia_actual.getDay())

var dissabte = new Date();
var diumenge = new Date();
if (dia_actual.getDay()<6) { //de dg a dv
  dissabte.setDate(dissabte.getDate() -dia_actual.getDay()-1);
  diumenge.setDate(diumenge.getDate() -dia_actual.getDay());
  var dissabtestr = dissabte.getFullYear() + "-" + ("0" + (dissabte.getMonth() + 1)).slice(-2) + "-" + dissabte.getDate()
  var diumengestr = diumenge.getFullYear() + "-" + ("0" + (diumenge.getMonth() + 1)).slice(-2) + "-" + diumenge.getDate()
} else { //dissabte
  dissabte.setDate(dissabte.getDate());
  diumenge.setDate(diumenge.getDate()+1);
  var dissabtestr = dissabte.getFullYear() + "-" + ("0" + (dissabte.getMonth() + 1)).slice(-2) + "-" + dissabte.getDate()
  var diumengestr = diumenge.getFullYear() + "-" + ("0" + (diumenge.getMonth() + 1)).slice(-2) + "-" + diumenge.getDate()
}
//console.log(dissabtestr)
//console.log(diumengestr)


var server = 'https://api.football-data.org/v2/matches?competitions=PD&dateFrom=' + dissabtestr + '&dateTo=' + diumengestr

var headers = {
  "X-Auth-Token": "*******"
}

fetch(server, { method: 'GET', headers: headers})
.then((res) => {
    //console.log(res)
    return res.json()
})
.then((json) => {
    //console.log(json)
    let goalsHome, goalAway
    for (let i=0; i<json.matches.length; i++) {
      if (json.matches[i].score.fullTime.homeTeam !== null) {
        goalsHome = json.matches[i].score.fullTime.homeTeam
        goalsAway = json.matches[i].score.fullTime.awayTeam
      } else {
        goalsHome = ""
        goalsAway = ""
      }
      console.log(json.matches[i].homeTeam.name + " - " + json.matches[i].awayTeam.name + ": " + goalsHome + "-" + goalsAway)
    }
})
$ node partits_jornada.js 
RC Celta de Vigo - Levante UD: -
Real Madrid CF - Villarreal CF: -
...

Estem al COVID-19, i els partits estan ajornats.

Partits disputats fins ara en la competició actual

script partits_barcelona.js:

//Barça és el id=81, i està hardcoded

const fetch = require('node-fetch')
var server = 'https://api.football-data.org/v2/competitions/PD/matches/'

var headers = {
  "X-Auth-Token": "*******"
}

fetch(server, { method: 'GET', headers: headers})
.then((res) => {
    //console.log(res)
    return res.json()
})
.then((json) => {
    //console.log(json)
    console.log("Partits del Barça del que portem de Lliga")
    for (let i=0; i<json.matches.length; i++) {
      if (json.matches[i].score.winner != null && (json.matches[i].homeTeam.id==81 || json.matches[i].awayTeam.id==81)) {
        local = json.matches[i].homeTeam.name
        visitant = json.matches[i].awayTeam.name
        resultat = json.matches[i].score.fullTime.homeTeam + "-" + json.matches[i].score.fullTime.awayTeam
        utcDate = json.matches[i].utcDate;
        var anyo = utcDate.substring(0, 4);
        var mes = utcDate.substring(5, 7);
        var dia = utcDate.substring(8, 10);
        data_formatada = dia + "/" + mes + "/" + anyo
        console.log(local + " - " + visitant + ": " + resultat + " (" + data_formatada + ")")
      }
    }
})
$ nodejs partits_barcelona.js 

Partits del Barça del que portem de Lliga
Athletic Club - FC Barcelona: 1-0 (16/08/2019)
FC Barcelona - Real Betis Balompié: 5-2 (25/08/2019)
CA Osasuna - FC Barcelona: 2-2 (31/08/2019)
FC Barcelona - Valencia CF: 5-2 (14/09/2019)
Granada CF - FC Barcelona: 2-0 (21/09/2019)
FC Barcelona - Villarreal CF: 2-1 (24/09/2019)
...

Instal·lació i configuració

$ cd ~/MagicMirror/modules
$ git clone https://github.com/fewieden/MMM-soccer

$ joe ~/MagicMirror/config/config.js

    config: {
            api_key:'********',
            colored:false,
            show:'SPAIN',
            focus_on:false,
            leagues:{"SPAIN":"PD"},
            logos:true
    }

$ npm i --production in ~/MagicMirror/modules/MMM-soccer

reiniciem:
$ pm2 restart mm

I funciona tal qual, però el codi està en la versió 1 de la API i el puc migrar a la v2 de l'API. De moment no hi ha res de MMM-voice.

encara no hi ha els logos. El script scripts/downloader.js està pensat per a la v1. El millor serà que els descarregui manualment.

wget http://upload.wikimedia.org/wikipedia/de/a/aa/Fc_barcelona.svg
wget http://upload.wikimedia.org/wikipedia/de/3/3f/Real_Madrid_Logo.svg
wget https://upload.wikimedia.org/wikipedia/de/c/c0/FC_Sevilla.svg
wget http://upload.wikimedia.org/wikipedia/de/5/55/Real_Sociedad_San_Sebasti%C3%A1n.svg
wget https://upload.wikimedia.org/wikipedia/en/7/7f/Getafe_logo.png
wget http://upload.wikimedia.org/wikipedia/de/c/c1/Atletico_Madrid_logo.svg
wget http://upload.wikimedia.org/wikipedia/de/7/75/FC_Valencia.svg
wget http://upload.wikimedia.org/wikipedia/de/7/70/Villarreal_CF_logo.svg
wget http://upload.wikimedia.org/wikipedia/de/d/d3/Granada_CF.svg
wget https://upload.wikimedia.org/wikipedia/en/9/98/Club_Athletic_Bilbao_logo.svg
wget http://upload.wikimedia.org/wikipedia/de/c/ca/Atletico_Osasuna.svg
wget http://upload.wikimedia.org/wikipedia/de/4/43/Real_Betis.svg
wget http://upload.wikimedia.org/wikipedia/de/1/1f/Levante_ud.svg
wget http://upload.wikimedia.org/wikipedia/en/2/2e/Deportivo_Alaves_logo.svg
wget http://upload.wikimedia.org/wikipedia/de/6/6e/Real_Valladolid_Logo.svg
wget http://upload.wikimedia.org/wikipedia/en/7/75/SD_Eibar_logo.svg
wget http://upload.wikimedia.org/wikipedia/de/0/0c/Celta_Vigo.svg
wget http://upload.wikimedia.org/wikipedia/de/e/e0/Rcd_mallorca.svg
wget https://www.joma-sport.com/ka/apps/joma_com_media/assets/sponsor/svg/129.svg
wget http://upload.wikimedia.org/wikipedia/de/a/a7/RCD_Espanyol_De_Barcelona.svg

i ara s'han de renombrar

cd public
mkdir logos


scritp /MMM-soccer.njk (línia 26):

<td><img class="icon {% if not config.colored %}no-color{% endif %}"
src="./modules/MMM-soccer/public/logos/{{standing[index].team.name.split(' ').join('_')}}.svg"/></td>

Per tant, el mòdul funciona, i està implementada la versió 2, com es pot veure al script node_helper.js:

    socketNotificationReceived(notification, payload) {
        if (notification === 'GET_DATA') {
            const options = {
                url: `http://api.football-data.org/v2/competitions/${payload.league}/standings`
            };
            if (payload.api_key) {
                options.headers = { 'X-Auth-Token': payload.api_key };
            }
            this.getData(options);
        }
    },
...

Modificacions i simplificació

  • Vull que d'inici la classificació (que ocupa l'espai central top-center, igual que el mòdul de fotos) estigui amagada (s'ha d'afegir una opció en el config que sigui visible:false).
  • Vull que respongui amb VOICE a les següents comandes: SHOW/HIDE CLASSIFICATION, SHOW/HIDE MATCHES
  • En el SHOW ha de desaparèixer el mòdul de fotos, i aparèixer el mode SOCCER
  • En el HIDE ha d'aparèixer el mòdul de fotos, i desaparèixer el mode SOCCER
  • Simplificar els noms dels equips: Real Betis Balompié -> Betis, etc.

El diccionari queda més simple, la part que fa referència a SOCCER seria només:

HIDE  HH AY D
SHOW  SH OW
SOCCER  S AA K ER
CLASSIFICATION K L A S IH F IH K AE SH OW N
MATCHES  M A T JH AE Z

Mòdul HoraCatalana

Ja sé fer sonar el Festival a la Raspberry Pi, i a més també sé fer-lo sonar amb NodeJS.

Per tant, puc integrar la veu (TTS) en els meus mòduls, modificar-los per afegir-hi veu, ni que sigui opcionalment.

I m'agradaria crear un mòdul que es digués HoraCatalana que li preguntis 'quina hora és, i et contesti són dos quarts i mig de set.

Construcció (TBD)

Aquí està molt ben explicat tot el tema de la construcció.

A mes dóna la idea de connectar-se amb Alexa per controlar el sistema per veu.


creat per Joan Quintana Compte, juliol 2019, abril 2020 (COVID-19)