Python, virtual environment e nuovi strumenti

Sto facendo un po’ di ordine tra i miei vecchi progetti in Python, non per una vera necessità, ma essenzialmente a scopo (auto) formativo, per rivedere ed aggiornare gli strumenti di sviluppo.

Il primo obiettivo è passare a Python 3. Oramai, dal 1 gennaio 2020, Python 2 è definitivamente sul viale del tramonto, molti strumenti smetteranno di supportarlo, tra cui pip.

Il secondo obiettivo è fare ordine nella gestione delle librerie e adottare finalmente un sistema di virtualizzazione dell’ambiente di sviluppo, in modo da separare le librerie utilizzate in ogni progetto. Mi sto trovando bene con pipenv, che raggruppa pip e virtualenv in uno strumento comune.

pipenv

Dopo qualche lustro con Eclipse (Java è un vecchio amore che non si scorda mai) sto passando a Visual Studio Code e si fa presto a capire come mai è così diffuso anche nel mondo Linux. Sono disponibili moltissime “extensions”, tra cui anche quella per supportare i multi environment con Python e pipenv.

Per non aggiungere troppa carne al fuoco, mantengo il mio fidato Subversion. A dire il vero, sono evidenti i benefici di un “distributed” VCS come GIT per lo sviluppo di progetti open-source di portata internazionale, meno in ambito aziendale, a parte il famoso “lo usano tutti”..

 

Nuova vita, nuova tastiera..

Dopo anni con una tastierona Logitech “gommosa”, arrivata oramai a fine vita, mi sono concesso una piccola soddisfazione.

Varmilo Va88M

Varmilo Va88M

Quasi un desiderio di rinnovamento. Lascio la mia vecchia attività e voglio aggiornare i miei strumenti di lavoro.
Ho ordinato direttamente dalla Cina una testiera meccanica Varmilo VA88M, personalizzata con layout ISO italiano.
Ho scelto degli switch Cherry Mx Brown, i keycaps sono in PBT con colorazione custom e retroilluminazione light-blu. Formato TKL, senza tastierino numerico.

VA88M ISO layout italiano

VA88M ISO layout italiano

La qualità è notevole, la si percepisce appena si inizia ad usarla sul serio. Sono consapevole che avrei dovuto scegliere un layout ANSI US, più adatto ad un programmatore, ma oramai sono anni che non uso più quel layout e sinceramente mi piaceva avere un oggetto che non si trova sul mercato, se non su personalizzazione.

Con Windows 10 non ci sono problemi, tutto funziona bene, anche i tasti multimediali funzionano correttamente,  richiamabili usando Fn+F7~F12. 

Purtroppo per il firmware della Varmilo qualsiasi OS che non sia Windows è MacOS. Perciò nella mia Ubuntu 20.04LTS, il comando lsusb restituisce:

Bus 001 Device 002: ID 05ac:024f Apple, Inc. Varmilo Keyboard

Il risultato è che la tastiera viene gestita dal modulo hid_apple, il quale per default lascia in prima battuta i tasti multimediali (che uso raramente) e per ottenere i tasti F1~F12 si deve premere Fn.
La soluzione però è semplice e definitiva:

echo options hid_apple fnmode=2 | sudo tee -a /etc/modprobe.d/hid_apple.conf
sudo update-initramfs -u -k all

Sembra ci sia anche la possibilità di modificare il firmware della tastiera e disabilitare definitivamente il supporto Apple.

 

Smart TV LG, Kodi vicino alla pensione?

Finalmente, grazie al Black Friday 2019 ho cambiato il mio vecchio TV Samsung LCD 40″.
Sono passato ad una Smart TV 49″ LG LED NanoCell 4K dotata di HDR, Dolby Vision™,  Dolby Atmos®, Google Assistant e Alexa.
Una delle prime TV con funzionalità VRR (frequenza di aggiornamento variabile), ALLM (modalità auto a bassa latenza) introdotte con HDMI 2.1.
WebOs, il sistema operativo di questa TV, supporta tutti i servizi di streaming più diffusi: PrimeVideo, Netflix, YouTube, Disney+, oltre che app come Plex o Emby che consentono la visualizzazione di contenuti presenti su NAS locali.
Dopo un periodo di prova, un po’ alla volta sto abbandonando Kodi.
Il motivo principale è la possibilità di sfruttare le funzionalità HDR e Dolby Vision, oltre che vedere contenuti Netflix in full HD e 4K, ad oggi impossibile con Kodi su Linux.
Emby e le app di streaming fornite da questa smart TV, sono in grado di sostituirlo senza troppi rimpianti.

 

LG 49SM9000PLA

LG 49SM9000PLA

 

Scraping con Python e WebKit2

Ho sviluppato una applicazione in Python e GTK3 per Ubuntu Linux per la gestione e l’invio degli ordini dei tabacchi. Nel corso del tempo ho cercato di rendere sempre più automatico e meno ripetitivo l’invio di un ordine e la raccolta delle informazioni sul suo stato.

Ho usato le librerie PyGObject per accedere ai bindings per WebKitGTK+, la mia piattaforma di riferimento è una Ubuntu 16.04.2 LTS.

apt install gir1.2-webkit2-4.0

Ho realizzato un mini-browser dedicato, che si occupa, tramite una sequenza di script, dell’autenticazione e della navigazione fino al punto da me desiderato ed in fase di chiusura, tramite un’altra sequenza di script, di ottenere informazioni sull’invio e lo stato degli ordini.

Invio ordine

Invio ordine

Uno dei problemi principali è stato ottenere risultati dall’esecuzione del codice Javascript usando solo Python, senza ricorrere a codice C.
Come si può vedere dalla documentazione online di WebKit2 4.0, l’esecuzione di codice Javascript tramite la classe Webkit2.Webview avviene tramite il metodo run_javascript che, tramite una callback,  grazie al metodo run_javascript_finish ritorna un oggetto JavascriptResult.
Il problema è che i bindings Python non gestiscono l’oggetto appena descritto e non si riesce ad estrarne informazioni utili.
Una soluzione, non eccessivamente complessa e neanche troppo “sporca” è quella di segnalare a Python l’arrivo di un messaggio proveniente da Javascript connettendo l’UserContentManager della WebView ad uno speciale segnale "script-message-received::XXX" da noi predefinito usando la clipboard come canale di trasferimento.
Javascript si occuperà di porre l’elemento da inviare nella clipboard e quindi di segnalare la sua disponibilità tramite window.webkit.messageHandlers.XXX.postMessage().

Di seguito un esempio per dimostrare questa soluzione usando GTK 3 e WebKit2 versione 4.


#!/usr/bin/python
# coding: UTF-8
#
# Copyright (C) Francesco Guarnieri 2017
# 

import gi
gi.require_version('WebKit2', '4.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, Gio, WebKit2, Pango

start_url = "http://www.google.it"

# Uno stack di script da eseguire al termine del caricamento di ogni pagina
scripts = ["""var range = document.createRange();
          var elemento = document.getElementsByClassName('g')[0];;
          range.selectNode(elemento);
          window.getSelection().removeAllRanges();
          window.getSelection().addRange(range);
          document.execCommand('copy');    
	  window.webkit.messageHandlers.CANALE_JS.postMessage('Test');""",
       
	"""document.getElementById('lst-ib').value = 'Il blog di Francesco Guarnieri'; 
	   document.getElementById('tsf').submit();"""	       
	]
			
class Browser(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_default_size(1000,600)
	self.scripts = None
	contentManager = WebKit2.UserContentManager() 
        contentManager.connect("script-message-received::CANALE_JS", self.__handleScriptMessage)
        if not contentManager.register_script_message_handler("CANALE_JS"):
            print("Error registering script message handler: CANALE_JS")
	
	# Inizializza Webview con il contentManager
        self.web_view = WebKit2.WebView.new_with_user_content_manager(contentManager)
	self.web_view.connect("load-changed", self.__loadFinishedCallback)    
            
        # Peronalizza i settings:
	# Nel nostro caso abilita Javascript ad accedere alla clipboard
        settings = WebKit2.Settings()
        settings.set_property('javascript-can-access-clipboard', True)
        self.web_view.set_settings(settings)

        okButton = Gtk.Button()
        icon = Gio.ThemedIcon(name="emblem-ok-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        okButton.add(image)
        okButton.connect("clicked", self.__close)
        cancelButton = Gtk.Button()
        icon = Gio.ThemedIcon(name="window-close-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        cancelButton.add(image)
        cancelButton.connect("clicked", self.__close)
        headerBar = Gtk.HeaderBar()
        headerBar.set_show_close_button(False)
	self.set_titlebar(headerBar)
        boxTitle = Gtk.Box(spacing = 6)
        self.spinner = Gtk.Spinner()
        labelTitle = Gtk.Label()
        labelFont = labelTitle.get_pango_context().get_font_description()
        labelFont.set_weight(Pango.Weight.BOLD)
        labelTitle.modify_font(labelFont)
        boxTitle.add(labelTitle)
        boxTitle.add(self.spinner)
        headerBar.set_custom_title(boxTitle)
        self.stopButton = Gtk.Button()      
	icon = Gio.ThemedIcon(name="media-playback-stop-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        self.stopButton.add(image)
	self.stopButton.connect("clicked", self.__on_stop_click)
        headerBar.pack_end(self.stopButton)
        box = Gtk.Box()
        Gtk.StyleContext.add_class(box.get_style_context(), "linked")
        box.add(okButton)
        box.add(cancelButton)
        headerBar.pack_start(box)
        browserBox = Gtk.Box()
        browserBox.set_orientation(Gtk.Orientation.VERTICAL)
        browserBox.pack_start(self.web_view, True, True, 0)
        self.add(browserBox)
	
    # Callback richiamata ad ogni cambiamento di stato della fase di load della webview
    # I possibili eventi: WEBKIT_LOAD_STARTED, WEBKIT_LOAD_REDIRECTED, 
    # WEBKIT_LOAD_COMMITTED, WEBKIT_LOAD_FINISHED
    def __loadFinishedCallback(self, web_view, load_event):
        if self.scripts and (len(self.scripts) > 0) and (load_event == WebKit2.LoadEvent.FINISHED):
            self.__waitMode(False)
	    web_view.run_javascript(self.scripts.pop(), None, self.__javascript_finished, None)
	return False
    
    # Callback per gestire la fine dell'esecuzione del codice javascript
    def __javascript_finished(self, webview, task, user_data = None):
        try:
	    # Qui si pone il problema dell'oggetto result di tipo JavascriptResult 
	    # non gestibile
            result = webview.run_javascript_finish(task)
        except Exception as e:
            print("JAVASCRIPT ERROR MSG: %s" % e)

    # Gestisce i messaggi ricevuti da Javascript anche in modo asincrono, non
    # necessariamente al termine dell'esecuzione 
    def __handleScriptMessage(self, contentManager, js_result):
        # Anche qui si pone il problema dell'oggetto js_result di tipo 
	# JavascriptResult non gestibile. Per ottenere risultati senza ricorrere
	# al codice C è possibile usare la clipboard       
        print "[ Ricevuto messaggio da JavaScript ]"
	clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        resultStr = clipboard.wait_for_text()
        if resultStr:
	     print "%s" % resultStr
	return True
    
    # Imposta la modalita di attesa se attiva o meno    
    def __waitMode(self, toggle):
        self.stopButton.set_sensitive(toggle)
        self.stopButton.set_visible(toggle)
        self.web_view.set_sensitive(not toggle)
        if toggle: self.spinner.start()
        else: self.spinner.stop()
    
    # Gestisce la pressione del pulsante di stop
    def __on_stop_click(self, widget):
        self.__waitMode(False)
        self.web_view.stop_loading()

    # Gestisce la chiusura del mini-browser
    def __close(self, widget=None):
        self.web_view.stop_loading()
        self.destroy()
        Gtk.main_quit()

    # Apre il sito
    def open(self, site, scripts = None):
	self.scripts = scripts
        self.__waitMode(True)
        self.web_view.load_uri(site)

if __name__ == "__main__":
    browser = Browser()
    browser.show_all()
    browser.open(start_url, scripts)
    Gtk.main()

Fibra TIM – Bella solo a metà

Bella la fibra.. Se abiti in città..
Ma oltre i 5/600 mt dall’armadio Telecom le cose degradano molto velocemente, fino ad avvicinarsi alle prestazioni di una normale adsl2.
Nel mio caso, disto circa 1 km dall’armadio dove realmente arriva la fibra ottica (FTTC, “fiber to the cabinet”), poi è il solito doppino in rame, che, grazie alla tecnologia Vdsl2 mi porta nel “magico mondo della fibra”.
In teoria dovrei avere 100mb in download e 20mb in upload (profilo 17a), in realtà queste sono le prestazioni della mia linea:

Fibra Tim

Fibra Tim

Da una parte mi accontento: ho quasi triplicato le prestazioni rispetto alla vecchia adsl. Se invece osservo “l’upgrade” da un’altra prospettiva, sono molto (ma molto) lontano da quello che potrei avere da questa tecnologia.
Speriamo in un affinamento nei sistemi di cancellazione del rumore e in nuove evoluzioni della tecnologia Vdsl2 (profilo 35b, magari con vectoring).

Beebox N3000 ed Ubuntu

Il mese scorso mi è stato “commissionato” un mediacenter di buona qualità, silenzioso, non eccessivamente costoso (il budget era massimo 200€), che potesse essere acceso ed usato con un telecomando e dove possibilmente poter riutilizzare un  disco SSD di piccola taglia.

Il Beebox N3000 mi è sembrato il candidato ideale, ad un prezzo di 133€ era imperdibile. Per 45€ ho aggiunto due moduli ram Corsair da 4GB DDR3 (1600 MHz, CL9), identici, per usufruire dei benefici della modalità dual-channel.

Beebox N3000

Beebox N3000

Questo piccolo gioiellino fanless si basa su un SoC Braswell con un Intel Dual-Core Celeron N3000 e dispone di una scheda di rete gigabit, un modulo WIFI 802.11ac e Bluetooth 4.0, 3 porte USB3, una porta USB3 Type C,  2 prese HDMI e 1 Displayport.

Inoltre, supporta dischi in formato mSata e anche Sata da 2,5″, insieme a due slot per memorie DDR3,  una presa ir e telecomando incluso.

Le mie istruzioni per realizzare un mediacenter basato su Ubuntu 16.04LTS con Kodi, si applicano a questo pc con poche modifiche, essenzialmente legate alla scheda video integrata nella CPU e un differente driver per la presa IR.

Senza entrare troppo nello specifico, posso testimoniare che questo mediacenter è in grado di visualizzare senza alcun affanno video full-hd con codifica H.265, per non parlare di H.264. Non ho potuto verificare con risoluzioni superiori (4K), ma forse siamo ai limiti delle potenzialità di questo ottimo sistema.

Macbook 2006 e Ubuntu 16.04 con EFI

In famiglia avevamo un vecchio Macbook 2006 che se ne stava in un cassetto già da molto tempo. Perchè non riportarlo a nuova vita?

Oltre OS X Lion 10.7.5 non era possibile andare e realisticamente con 1gb di ram e un hard-disk da 5400rpm eravamo già ai limiti.

Grazie ad un precedente “upgrade” del mio mediacenter avevo un disco SSD da 64gb che mi avanzava, un vecchio portatile con lo schermo rotto e mai riparato (antieconomico) avrebbe fornito la ram per espandere il Macbook al massimo di 3gb.

Ho deciso di passare ad Ubuntu 16.04.1 LTS.  Ho scelto la versione a 64bit, visto che il portatile ha una cpu Intel Core 2 Duo.

Anche in questo caso (vedi il mio post su Lenovo Ideapad Flex 10) mi sono trovato con un problema di sistemi EFI a 32bit con sistemi potenzialmente a 64bit.
Neanche l’immagine di sistema più recente (con Ubuntu 16.04.1) supporta questa situazione ibrida. Ho dovuto quindi cercare delle soluzioni per generare un’immagine da scrivere sulla mia chiavetta usb che potesse essere eseguita dal bios EFI a 32bit del Macbook.

Questa è la sequenza delle istruzioni (da eseguire con diritti di root su un sistema con una Ubuntu 16.04), che ho lievemente modificato rispetto all’originale dedicato ad un ASUS EeeBook:

apt install p7zip-full

wget http://releases.ubuntu.com/16.04/ubuntu-16.04.1-desktop-amd64.iso

# Suppongo che la chiavetta USB sia sul device  /dev/sdb
# La chiavetta non deve essere montata su nessuna directory
# ATTENZIONE! QUESTO CANCELLERA' TUTTO QUELLO CHE STA SU /dev/sdb !
sgdisk --zap-all /dev/sdb
sgdisk --new=1:0:0 --typecode=1:ef00 /dev/sdb
mkfs.vfat -F32 /dev/sdb1

# Monto la chiavetta sulla directory /mnt e
# ci copio il file ISO:
mount -t vfat /dev/sdb1 /mnt
7z x ubuntu-16.04.1-desktop-amd64.iso -o/mnt/ 

# Crea il bootloader a 32bit e lo scrive sulla chiavetta:
apt install git bison libopts25 libselinux1-dev autogen m4 autoconf help2man libopts25-dev 
apt install flex libfont-freetype-perl automake autotools-dev libfreetype6-dev texinfo
git clone git://git.savannah.gnu.org/grub.git
cd grub
./autogen.sh
./configure --with-platform=efi --target=i386 --program-prefix=''
make
cd grub-core
../grub-mkimage -d . -o bootia32.efi -O i386-efi -p /boot/grub \
  ntfs hfs appleldr boot cat efi_gop efi_uga elf fat hfsplus iso9660 linux keylayouts \
  memdisk minicmd part_apple ext2 extcmd xfs xnu part_bsd part_gpt search search_fs_file \
  chain btrfs loadbios loadenv lvm minix minix2 reiserfs memrw mmap msdospart scsi loopback \
  normal configfile gzio all_video efi_gop efi_uga gfxterm gettext echo boot chain eval
cp bootia32.efi /mnt/EFI/BOOT/
umount /mnt

Ricordandosi che è necessario premere il tasto “Option” in fase di boot, altrimenti non potremo scegliere il boot dalla partizione EFI della chiavetta.

Finalmente,  potremo installare una bella Ubuntu sul nostro Macbook, con tabella delle partizioni GPT e pronta per il boot da EFI.

Ma il lavoro, purtroppo, non è ancora finito.. Alla fine dell’installazione, ci accorgeremo che il boot da disco non funziona e che il solo modo per eseguire Ubuntu è affidarsi alla partizione di boot dalla nostra fidata chiavetta.

Qualcosa ancora non va. Finalmente, grazie ad un ottimo articolo ho trovato la spiegazione e la soluzione del mio problema: la partizione EFI deve essere formattata con il filesystem Mac HFS+ (e non Fat32) e devono essere presenti alcuni specifici files.

Nell’articolo appena menzionato, viene spiegato come installare Ubuntu su Mac recenti (dal 2012 in poi) che ovviamente non hanno i problemi che ho affrontato per creare il disco di installazione (sono sistemi full 64bit).

Supponendo che la partizione EFI sia la numero 1, riporto di seguito le istruzioni leggermente modificate per un EFI a 32 bit:

# Repository con utilities per Intel Mac (fan control, HFS bless, bootloader icon, etc..)
add-apt-repository ppa:detly/mactel-utils
apt update
apt install mactel-boot hfsprogs gdisk grub-efi-ia32

umount /dev/sda1 
# Modifica la partizione EFI /dev/sda1 
gdisk /dev/sda

Questi sono i comandi da eseguire con gdisk:

Command (? for help): d
Partition number (1-3): 1

Command (? for help): n
Partition number (1-128, default 1): 1
«accept defaults»

Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): AF00
Changed type of partition to 'Apple HFS/HFS+'

Command (? for help): w

Formatta la partizione EFI e Aggiorna fstab:

mkfs.hfsplus /dev/sda1 -v Ubuntu
bash -c 'echo $(blkid -o export -s UUID /dev/sda1) /boot/efi auto defaults 0 0 >> /etc/fstab'
mount /boot/efi

Installa Grub:

mkdir -p "/boot/efi/EFI/$(lsb_release -ds)/"
bash -c 'echo "This file is required for booting" > "/boot/efi/EFI/$(lsb_release -ds)/mach_kernel"'
bash -c 'echo "This file is required for booting" > /boot/efi/mach_kernel'
grub-install --target i386-efi --boot-directory=/boot --efi-directory=/boot/efi --bootloader-id="$(lsb_release -ds)"
hfs-bless "/boot/efi/EFI/$(lsb_release -ds)/System/Library/CoreServices/boot.efi"

Kodi ed ubuntu – guida all’installazione

Ho appena pubblicato una pagina per la guida all’installazione del mediacenter Kodi su Ubuntu 16.04 LTS.

La guida è molto dettagliata, ma richiede un minimo di dimestichezza con Linux e la shell bash.
Prevede la configurazione con script Systemd per il boot automatico di Kodi e per sostituire Grub con Extlinux ed avere un dual boot con splash screen full HD.

Spiego come personalizzare il display LCD del mio case Antec Fusion Black ed usare il mediacenter con un telecomando Logitech Harmony usando ir-keytable al posto di Lirc.

Cercherò di tenerla aggiornata con le novità e i miglioramenti che si presenteranno in futuro.

Come difendersi dalle vendite telefoniche

Nonostante mi sia iscritto al registro delle opposizioni, continuo a ricevere telefonate non volute, che mi propongono (spesso con modi truffaldini) le cose più inutili.

Finalmente, credo di aver trovato una soluzione, anche se non a costo zero.

Sulla mia linea telefonica fissa ho attivato l’opzione “Chi è” di Telecom Italia (adesso TIM) alla “modica” cifra di 2,90€ mensili. Quindi ho cambiato il mio vecchio cordless con un nuovo Siemens Gigaset S850.

Siemens Gigaset S850

Siemens Gigaset S850

Tutto questo, per poter avere a disposizione una “blacklist”, funzione che esiste da tempo nel mondo mobile.
Prima di tutto, ho bloccato tutte le chiamate anonime. Successivamente, ogni chiamata non voluta è stata aggiunta alla blacklist, in modalità “blocco”, così da non avere il minimo fastidio, neanche un lampeggio.

Speriamo che i 15 numeri messi a disposizione siano sufficienti, io sarei stato più generoso.