Skip to content

Commit

Permalink
Rewrite to normal websockets, add support for Windows and add a GUI m…
Browse files Browse the repository at this point in the history
…ode, to ultimately replace JulianaNFC_C
  • Loading branch information
Kurocon committed Jul 12, 2022
1 parent 14b3504 commit 07f6b78
Show file tree
Hide file tree
Showing 12 changed files with 512 additions and 159 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ MANIFEST
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
Expand Down
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
# JulianaNFC
JulianaNFC is een Python-programmaatje dat pollt voor NFC tags en deze vervolgens over een WebSocket verstuurt. Werkt alleen onder Linux voor zover bekend.
JulianaNFC is een Python-programmaatje voor Windows en Linux dat pollt voor NFC tags en deze vervolgens over een WebSocket verstuurt.

## Hoe dan?
Installeer onder Linux de PC/SC Smart Card daemon ( [PCSClite](https://pcsclite.alioth.debian.org/pcsclite.html) - [Debian PKG](https://packages.debian.org/source/stretch/pcsc-lite) ) en zorg dat deze gestart is. Installeer de requirements uit requirements.txt en start juliana.py. Vanuit een browser connect je met de WebSocket.

socket = new WebSocket("ws://localhost:3000", "nfc");
### Windows
Geen requirements, Windows heeft een ingebouwde PC/SC Smart Card service.

Als er een kaart wordt gescand ontvang je een JSON-object met kaartinfo over de socket.
- Download de [release executable](https://github.com/Inter-Actief/JulianaNFC_Python/releases) voor Windows (`JulianaNFC_vX.X.exe`)
- Zet hem op een mooi plekje neer
- Start daarna `JulianaNFC_vX.X.exe`, de GUI zal starten.

{"atqa":"12:34", "uid":"ab:cd:ef:gh", "sak":"56"}
### Linux

#### Requirements
Installeer onder Linux de PC/SC Smart Card daemon ( [PCSClite](https://pcsclite.alioth.debian.org/pcsclite.html) - [Debian PKG](https://packages.debian.org/source/stretch/pcsc-lite) ) en zorg dat deze gestart is.

#### JulianaNFC - Optie 1
- Download de [release executable](https://github.com/Inter-Actief/JulianaNFC_Python/releases) voor Linux (`JulianaNFC_linux_vX.X`)
- Zet hem op een mooi plekje neer
- Start het executable bestand `JulianaNFC_linux_vX.X`, de GUI zal starten. (draai het met de `-h` flag voor CLI-opties)

#### JulianaNFC - Optie 2
- Clone of download de code van github
- Maak een virtualenv als je dat graag wilt
- Installeer de requirements uit requirements.txt
- Voer `python juliana.py` uit, de GUI zal starten. (zie `juliana.py -h` voor CLI-opties)

### Websocket
Vanuit een browser connect je met de WebSocket.

socket = new WebSocket('ws://localhost:3000', 'nfc');
socket.onmessage = function (event) {
var rfid = JSON.parse(event.data);
console.log("Tag scanned!");
console.log(rfid);
};

Als er een kaart wordt gescand ontvang je in het 'nfc_read' event een JSON-object met kaartinfo over de socket.

{"type": "iso-x", "atqa":"12:34", "uid":"ab:cd:ef:gh", "sak":"56"}

En dat is alles wat JulianaNFC doet.

## Bugs en to-do's
Bij het scannen van sommige kaarten (i.e. ID-kaarten) breekt de NFC lezer (onder Linux). Even de lezer opnieuw inpluggen fixt het dan vaak.

WebSockets zijn zéér gebrekkig geïmplementeerd. Zodra iets niet volgens plan gebeurt crasht JulianaNFC.
## Package bouwen
- Zoek een computer met het gewenste doelbesturingssysteem en installeer Python en alle requirements voor JulianaNFC.
- Noot: Gebruik bij packagen op Windows een python-versie waarvoor een [.whl van wxPython](https://pypi.org/project/wxPython/#files) beschikbaar is, dit package zelf bouwen op Windows is lastig.
- Installeer pyinstaller (`pip install pyinstaller`) (Getest met 5.2, maar zou met nieuwere prima moeten werken)
- Open een command prompt / terminal en ga naar de map waar JulianaNFC staat
- Verwijder de `build` en `dist` folders als deze nog bestaan van een vorige build
- Draai Pyinstaller op de specfile (`pyinstaller juliana_nfc.spec`)
- De build zal in de `build` folder plaatsvinden, de executable zal in de `dist` folder geplaatst worden.
55 changes: 55 additions & 0 deletions gui/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import wx
import wx.adv

from juliana import resource_path

class AboutDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
from juliana import APP_SUPPORT, APP_LINK
kwargs["style"] = kwargs.get("style", 0) | wx.DEFAULT_DIALOG_STYLE
wx.Dialog.__init__(self, *args, **kwargs)
self.SetSize((400, 350))
self.SetIcon(wx.Icon(resource_path("resources/main.png"), type=wx.BITMAP_TYPE_PNG))
self.hyperlink_2 = wx.adv.HyperlinkCtrl(self, wx.ID_ANY, APP_LINK, APP_LINK, style=wx.adv.HL_ALIGN_CENTRE)
self.hyperlink_3 = wx.adv.HyperlinkCtrl(self, wx.ID_ANY, APP_SUPPORT, f"mailto://{APP_SUPPORT}", style=wx.adv.HL_ALIGN_CENTRE)
self.button_2 = wx.Button(self, wx.ID_OK, "")

self.__set_properties()
self.__do_layout()

def __set_properties(self):
self.SetTitle(f"About JulianaNFC")
self.SetSize((400, 350))

def __do_layout(self):
from juliana import APP_NAME, APP_VERSION, APP_AUTHOR
grid_sizer_1 = wx.BoxSizer(wx.VERTICAL)
grid_sizer_1.Add((0, 10), 0, wx.EXPAND, 0)
label_1 = wx.StaticText(self, wx.ID_ANY, f"{APP_NAME} v{APP_VERSION}", style=wx.ALIGN_CENTER)
label_1.SetFont(wx.Font(18, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
grid_sizer_1.Add(label_1, 0, wx.ALIGN_CENTER, 8)
grid_sizer_1.Add((0, 8), 0, wx.EXPAND, 0)
static_line_1 = wx.StaticLine(self, wx.ID_ANY)
grid_sizer_1.Add(static_line_1, 0, wx.ALL | wx.EXPAND, 0)
grid_sizer_1.Add((0, 8), 0, wx.EXPAND, 0)
label_2 = wx.StaticText(self, wx.ID_ANY, f"JulianaNFC is a small tray application that allows scanning "
f"NFC cards to a websocket for use in web applications.", style=wx.ALIGN_CENTER)
label_2.Wrap(320)
grid_sizer_1.Add(label_2, 0, wx.ALIGN_CENTER, 8)
grid_sizer_1.Add((0, 8), 0, wx.EXPAND, 0)
label_3 = wx.StaticText(self, wx.ID_ANY, f"JulianaNFC was created by {APP_AUTHOR}", style=wx.ALIGN_CENTER)
label_3.Wrap(320)
grid_sizer_1.Add(label_3, 0, wx.ALIGN_CENTER, 8)
grid_sizer_1.Add((0, 8), 0, wx.EXPAND, 0)
label_6 = wx.StaticText(self, wx.ID_ANY, "For more information, check the GitHub:", style=wx.ALIGN_CENTER)
grid_sizer_1.Add(label_6, 0, wx.ALIGN_CENTER, 0)
grid_sizer_1.Add(self.hyperlink_2, 0, wx.ALIGN_CENTER, 0)
label_7 = wx.StaticText(self, wx.ID_ANY, "For support, mail the WWW-committee:", style=wx.ALIGN_CENTER)
grid_sizer_1.Add(label_7, 0, wx.ALIGN_CENTER, 0)
grid_sizer_1.Add(self.hyperlink_3, 0, wx.ALIGN_CENTER, 0)
grid_sizer_1.Add((0, 10), 0, wx.EXPAND, 0)
grid_sizer_1.Add(self.button_2, 0, wx.ALIGN_CENTER, 0)
grid_sizer_1.Add((0, 10), 0, wx.EXPAND, 0)
self.SetSizer(grid_sizer_1)
grid_sizer_1.Fit(self)
self.Layout()
120 changes: 120 additions & 0 deletions gui/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import wx
import wx.adv
import signal
import os
import time
import logging

from juliana import resource_path


logging.basicConfig(level=logging.INFO)


class JulianaTrayIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
super(JulianaTrayIcon, self).__init__()
self.SetIcon(wx.Icon(resource_path("resources/main.png"), type=wx.BITMAP_TYPE_PNG), "JulianaNFC")
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.on_left_click)

def CreatePopupMenu(self):
self.menu = wx.Menu()
self.create_menu_text(self.menu, "JulianaNFC")
self.menu.AppendSeparator()
self.create_menu_item(self.menu, "About", self.on_about)
self.create_menu_item(self.menu, "Exit", self.on_exit)
return self.menu

def create_menu_item(self, menu, label, callback, icon=None):
item = wx.MenuItem(menu, -1, label)
menu.Bind(wx.EVT_MENU, callback, id=item.GetId())
menu.Append(item)
if icon is not None:
bitmap = wx.Bitmap(icon, type=wx.BITMAP_TYPE_PNG)
item.SetBitmaps(checked=bitmap, unchecked=bitmap)
return item

def create_menu_text(self, menu, label, icon=None):
item = wx.MenuItem(menu, -1, label)
menu.Append(item)
item.Enable(False)
if icon is not None:
bitmap = wx.Bitmap(icon, type=wx.BITMAP_TYPE_PNG)
item.SetBitmaps(checked=bitmap, unchecked=bitmap)
return item

def on_left_click(self, event):
if self.frame.IsShown():
logging.info("Tray icon was left-clicked. Hiding main window")
self.frame.Show(False)
else:
logging.info("Tray icon was left-clicked. Showing main window")
self.frame.Show(True)

def on_about(self, event):
logging.debug("Tray option about clicked.")
from gui.about import AboutDialog
dialog = AboutDialog(None, wx.ID_ANY, "")
dialog.ShowModal()
dialog.Destroy()

def on_exit(self, event):
logging.info("Tray option exit clicked. Exiting Juliana")
self.RemoveIcon()
wx.CallAfter(self.Destroy)
self.frame.Close(force=True)


class JulianaApp(wx.App):
def __init__(self, *args, **kwargs):
super(JulianaApp, self).__init__(*args, **kwargs)
bitmap = wx.Bitmap(resource_path('resources/splash.png'))
self.splash = wx.adv.SplashScreen(bitmap, wx.adv.SPLASH_CENTER_ON_SCREEN | wx.adv.SPLASH_TIMEOUT, 3500, self.frame)

def OnInit(self):
from juliana import APP_NAME, APP_VERSION, APP_AUTHOR, APP_SUPPORT
self.frame = wx.Frame(None, title="JulianaNFC", size=(640, 480))
self.frame.SetIcon(wx.Icon(resource_path("resources/main.png"), type=wx.BITMAP_TYPE_PNG))

self.panel = wx.Panel(self.frame)

self.console = wx.TextCtrl(self.panel, style=(wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_BESTWRAP))
self.console.AppendText(f"{APP_NAME} v{APP_VERSION} (By {APP_AUTHOR})\n")
self.console.AppendText(f"Support: {APP_SUPPORT}\n\n")

self.horizontal = wx.BoxSizer(wx.HORIZONTAL)
self.horizontal.Add((8, 0), 0, wx.EXPAND, 0)
self.horizontal.Add(self.console, proportion=1, flag=wx.EXPAND)
self.horizontal.Add((8, 0), 0, wx.EXPAND, 0)

self.vertical = wx.BoxSizer(wx.VERTICAL)
self.vertical.Add((0, 8), 0, wx.EXPAND, 0)
self.vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND)
self.vertical.Add((0, 8), 0, wx.EXPAND, 0)

self.panel.SetSizerAndFit(self.vertical)
self.frame.Bind(wx.EVT_CLOSE, self.OnClose)

self.SetTopWindow(self.frame)
JulianaTrayIcon(self.frame)
return True

def OnClose(self, evt):
if evt.CanVeto():
logging.info("Main window closed. Hiding to systray")
self.frame.Show(False)
evt.Veto()
else:
logging.info("Main window closed and we must stop. Exiting Juliana")
wx.CallAfter(self.Destroy)
evt.Skip()

def OnExit(self):
os.kill(os.getpid(), signal.SIGINT)
time.sleep(1)
os.kill(os.getpid(), signal.SIGKILL)
return 0

def add_message(self, message):
self.console.AppendText(f"{message}\n")
Loading

0 comments on commit 07f6b78

Please sign in to comment.