Skip to content

Commit 7065021

Browse files
authored
Merge pull request #1 from v1605/api-v2
Api v2
2 parents a3799de + 827bd6f commit 7065021

File tree

4 files changed

+153
-5725
lines changed

4 files changed

+153
-5725
lines changed

README.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,50 @@
11
# TapTo Esp32
22

3-
The goal of this project is to launch a game via the [TapTo Service](https://github.com/TapToCommunity/tapto) on the a Mister over Wifi. It should work with any Ardunio compatible board but only Esp32 models are official supported.
3+
The goal of this project is to launch games via the [TapTo Service](https://github.com/TapToCommunity/tapto) on the a Mister over Wifi. It also supports launching games via [Simple Serial](https://tapto.wiki/Reader_Drivers#Simple_Serial).
44

5-
As of now, this code should be considered alpha, as it is not using the TapTo 2.0 api (instead using v1 endpoints). Once TapTo 2.0 is released I will rework the api calls.
5+
The project is now supporting the intial release of the v2 api (which uses websockets). Further updates are required once the security layer is implemented in the api.
66

7-
## Required Parts
7+
## Hardare
88
* An Esp32
99
* A MFRC522
10+
* (Optional) A MAX98357a board and speaker
11+
* (Optional) PWM Rumble motor. You can use a preconstructed board or build your own using a transistor and motor.
1012

11-
## Required Libaries
12-
* ArduinoJson (future TapTo 2.0)
13-
* UrlEncode
13+
## Required Libaries (Which may have their own dependencies)
1414
* MFRC55
1515
* ESP8266Audio
1616
* [NDEF](https://github.com/don/NDEF/tree/master)
17+
* [TapTo Esp32 Launch Api](https://github.com/v1605/tapto-esp32-launch-api)
1718

1819
## Setup
1920
1. If you haven't already, downalod and install Ardunio Studio.
2021
2. Install the above Libaries.
2122
3. Clone/Download the repository and open Tap2Esp32.ino in Arunido Studio.
2223
4. Select your board and port, located in the Tools menu. If you do not have options for an Esp32, follow these [instrustions](https://docs.sunfounder.com/projects/umsk/en/latest/03_esp32/esp32_start/03_install_esp32.html) for installing the board configurations.
2324
5. Edit the ReadTag.hpp file to define your pins for the MFRC522, the Mister url (using the Remote port until TapTo 2.0, default 8182), and your Wifi credentials.
24-
6. (Optional) If you are planning to use a battery to power the Esp32, it might help to extend battery life by decreasing the CPU frequency under tools.
25+
6. (Optional) If you are planning to use a battery to power the Esp32, it might help to extend battery life by decreasing the CPU frequency under tools. If using the audio configuration, you need a min cpu of 160mhz.
2526
7. (Optional) Enable any of the settings in the optional section of the config by uncommenting the line and setting the correct value for your setup. See "Optional Setup" for more info.
27+
8. (Optional) If using audio options, make sure your "Partion Scheme" under tools is set to "Default xMB with spiffs". xMB will vary based on your board, but 4MB is common. After you upload the project, follow the instructons [here](https://randomnerdtutorials.com/arduino-ide-2-install-esp32-littlefs/) to upload a mp3 (test with [this gb sound effect](https://tuna.voicemod.net/sound/e4674ff7-386c-4932-9faf-e50c82d45099)).
28+
9. Compile and upload the project your esp32.
2629

2730
## Optional Setup
2831
These descriptions are for the optional config options found in the ReadTag.hpp file. Uncommment and edit the lines in the config if you want to enable them.
29-
1. MOTOR_PIN: Sends a pulse to this pin when WiFi is connected, a game is launched, or some error has occurred (see Error Feedback). It is intended for a small vibration motor to provide haptic feedback.
30-
2. WIFI_LED_PIN: This pin will enter a high state once a WiFi connection has been established.
31-
3. LAUNCH_LED_PIN: This pin mimics the behaviors the MOTOR_PIN, with a slightly different pulse behavior more suitable for an LED.
32-
4. EXTERNAL_POWER_LED: This pin will enter a High state as soon as the unit boots. This feature is useful if your enclousre blocks any built in LEDs.
33-
5. I2S_*: These pins are for a I2S module (such as a max98357a) to produce a launch sound. You can replace the sound in the launchAudio.h file by converting a 8-bit unsigned pcm audio file to hex.
32+
1. SERIAL_ONLY: Uncommenting this setting will disable wifi. Only Simple Serial will be supported in this mode.
33+
2. MOTOR_PIN: Sends a pulse to this pin when WiFi is connected, a game is launched, or some error has occurred (see Error Feedback). It is intended for a small vibration motor to provide haptic feedback.
34+
3. WIFI_LED_PIN: This pin will enter a high state once a WiFi connection has been established.
35+
4. LAUNCH_LED_PIN: This pin mimics the behaviors the MOTOR_PIN, with a slightly different pulse behavior more suitable for an LED.
36+
5. EXTERNAL_POWER_LED: This pin will enter a High state as soon as the unit boots. This feature is useful if your enclousre blocks any built in LEDs.
37+
6. I2S_*: These pins are for a I2S module (such as a max98357a) to produce a launch sound. The audio file must be a mp3 file uploaded via LittleFs as described above.
38+
7. launchAudio: The name of the mp3 file that plays when a tag is launched.
39+
8. AUDIO_GAIN: How loud the speaker should be.
3440

3541
## Error Feedback
3642
If you choose to enable the Motor or Launch pins in the config file, you can get additional feedback when scanning a card:
37-
* 2 Pulses: Game failed to launch via TapTo.
43+
* 2 Pulses: Could not connect to TapTo
44+
* 3 Pulses: TapTo could not read the text, check your tag
45+
* 4 Pulses: TapTo sent a payload that could not be parsed to JSON.
46+
47+
## Portable Options
48+
It is possible to incorporate a battery to make a wireless NFC adapter. There are a two options to consider.
49+
1. An Adadfruit Feather Esp32: it has all the required hardware to run off a lipo battery. The downside is that even with a power switch to ground the enable pin, you can still get battery drain. If you add a swich on the battery, you can only charge when its powered on.
50+
2. Combine a lipo charging board, 5v buck converter, and a switch to power the board. This option require more assembly and reasearch but avoids the downsides of option one.

TapToEsp32.hpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
//Reguired Configuration-------------------------------------------------
55

66
//The SS/SDA and Reset pins of the MFRC522
7-
#define SS_PIN 12
8-
#define RST_PIN 27
7+
#define SS_PIN 5
8+
#define RST_PIN 16
99

1010
//Wifi and mister information
1111
const char* ssid = "NetworkName";
1212
const char* password = "Password";
13-
const String tapToUrl = "http://mister.local:7497";
13+
const String tapToUrl = "ws://mister.local:7497";
14+
15+
//Uncomment if serial only, wifi credentials will not be used
16+
//#define SERIAL_ONLY
1417

1518
//-----------------------------------------------------------------------
1619

@@ -20,18 +23,20 @@ const String tapToUrl = "http://mister.local:7497";
2023
//#define MOTOR_PIN 25
2124

2225
//Uncomment if using a led to display wifi status
23-
//#define WIFI_LED_PIN 16
26+
//#define WIFI_LED_PIN 4
2427

2528
//Uncomment if using a led to display launch status
26-
//#define LAUNCH_LED_PIN 16
29+
//#define LAUNCH_LED_PIN 33
2730

2831
//Uncomment if using a led to display power indicator
29-
//#define EXTERNAL_POWER_LED 16
32+
//#define EXTERNAL_POWER_LED 22
3033

3134
//Uncomment if using a I2S module to produce audio
3235
//#define I2S_DOUT 26
3336
//#define I2S_BCLK 17
3437
//#define I2S_LRC 21
38+
//#define AUDIO_GAIN 1 //Value 0.1 - 4
39+
//const char* launchAudio = "/gbc-startup.mp3";
3540

3641
//-----------------------------------------------------------------------
3742
#endif

TapToEsp32.ino

Lines changed: 116 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
#include <SPI.h>
22
#include <MFRC522.h>
33
#include <WiFi.h>
4-
#include <HTTPClient.h>
5-
#include <UrlEncode.h>
6-
#include "NfcAdapter.h"
7-
#include "AudioFileSourcePROGMEM.h"
8-
#include "AudioGeneratorWAV.h"
9-
#include "AudioOutputI2S.h"
10-
#include "launchAudio.h"
4+
#include <NfcAdapter.h>
5+
#include <AudioFileSourceLittleFS.h>
6+
#include <AudioOutputI2S.h>
7+
#include <AudioGeneratorMP3.h>
8+
#include <LittleFS.h>
9+
#include <TapToLaunchApi.h>
1110
#include "TapToEsp32.hpp"
1211

1312
//Config found in ReadTag.hpp
1413

1514
MFRC522 mfrc522(SS_PIN, RST_PIN);
1615
NfcAdapter nfc = NfcAdapter(&mfrc522);
16+
TapToLaunchApi client;
1717
AudioOutputI2S* out;
18+
boolean wifiEnabled = false;
1819

19-
void setup(void) {
20-
Serial.begin(9600);
20+
void setup() {
21+
Serial.begin(115200);
2122
setupPins();
23+
#ifndef SERIAL_ONLY
2224
initWiFi();
25+
client.url(tapToUrl);
26+
#endif
2327
SPI.begin(); // Init SPI bus
2428
mfrc522.PCD_Init(); // Init MFRC522
2529
nfc.begin();
@@ -42,125 +46,135 @@ void setupPins(){
4246
#ifdef I2S_DOUT
4347
out = new AudioOutputI2S();
4448
out->SetPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
49+
out->SetChannels(1);
50+
out->SetGain(AUDIO_GAIN);
51+
if (!LittleFS.begin(true)) {
52+
Serial.println("An error has occurred while mounting LittleFS. No launch audio");
53+
}
54+
else if (!LittleFS.exists(launchAudio)) {
55+
Serial.println("Launch audio file not found");
56+
}
4557
#endif
4658
}
4759

48-
void loop(void) {
49-
if (nfc.tagPresent()) {
50-
NfcTag tag = nfc.read();
51-
if(tag.hasNdefMessage()){
52-
NdefMessage message = tag.getNdefMessage();
53-
int recordCount = message.getRecordCount();
54-
NdefRecord record = message.getRecord(0);
55-
int payloadLength = record.getPayloadLength();
56-
const byte *payload = record.getPayload();
57-
String payloadAsString = "";
58-
for (int i = 3; i < payloadLength; i++) {
59-
payloadAsString += (char)payload[i];
60-
}
61-
sendTapTo(payloadAsString);
62-
nfc.haltTag();
63-
delay(1000);
64-
}
65-
}
66-
delay(200);
60+
void motorOn(int predelay=0){
61+
#ifdef MOTOR_PIN
62+
delay(predelay);
63+
analogWrite(MOTOR_PIN, 255);
64+
#endif
6765
}
6866

69-
void initWiFi() {
70-
WiFi.mode(WIFI_STA);
71-
WiFi.begin(ssid, password);
72-
Serial.print("Connecting to WiFi ..");
73-
while (WiFi.status() != WL_CONNECTED) {
74-
Serial.print('.');
75-
triggerWifiLed(HIGH, 0);
76-
delay(500);
77-
triggerWifiLed(LOW, 500);
78-
}
79-
Serial.println(WiFi.localIP());
80-
triggerWifiLed(HIGH, 0);
81-
triggerMotor(250, 2, 100);
67+
void motorOff(int predelay=0){
68+
#ifdef MOTOR_PIN
69+
delay(predelay);
70+
analogWrite(MOTOR_PIN, 0);
71+
#endif
8272
}
8373

84-
void sendTapTo(String gamePath){
85-
HTTPClient http;
86-
http.begin(tapToUrl + "/api/v1/launch/" + urlEncode(gamePath));
87-
int httpResponseCode = http.GET();
88-
if (httpResponseCode == 200) {
89-
Serial.println("Launched");
90-
triggerLaunchLed(HIGH, 0);
91-
triggerMotor(200, 1, 0);
92-
playAudio();
93-
triggerLaunchLed(LOW, 2000);
94-
}else{
95-
Serial.print("Error code: ");
96-
Serial.println(httpResponseCode);
97-
expressError(2);
98-
}
99-
http.end();
74+
void launchLedOn(int predelay=0){
75+
#ifdef LAUNCH_LED_PIN
76+
delay(predelay);
77+
digitalWrite(LAUNCH_LED_PIN, HIGH);
78+
#endif
10079
}
10180

102-
void triggerMotor(int time, int loopCount, int loopDelay){
103-
#ifdef MOTOR_PIN
104-
int last = loopCount - 1;
105-
for(int i=0; i <= last; i++){
106-
digitalWrite(MOTOR_PIN, HIGH);
107-
delay(time);
108-
digitalWrite(MOTOR_PIN, LOW);
109-
if(loopDelay > 0 && i != last){
110-
delay(loopDelay);
111-
}
112-
}
81+
void launchLedOff(int predelay=0, int postDelay=0){
82+
#ifdef LAUNCH_LED_PIN
83+
delay(predelay);
84+
digitalWrite(LAUNCH_LED_PIN, LOW);
85+
delay(postDelay);
11386
#endif
11487
}
11588

116-
void triggerLaunchLed(int state, int preDelay){
117-
#ifdef LAUNCH_LED_PIN
118-
if(preDelay > 0){
119-
delay(preDelay);
120-
}
121-
digitalWrite(LAUNCH_LED_PIN, state);
89+
void wifiLedOn(){
90+
#ifdef WIFI_LED_PIN
91+
digitalWrite(WIFI_LED_PIN, HIGH);
12292
#endif
12393
}
12494

125-
void triggerWifiLed(int state, int preDelay){
95+
void wifiLedOff(){
12696
#ifdef WIFI_LED_PIN
127-
if(preDelay > 0){
128-
delay(preDelay);
129-
}
130-
digitalWrite(WIFI_LED_PIN, state);
97+
digitalWrite(WIFI_LED_PIN, LOW);
13198
#endif
13299
}
133100

134101
void expressError(int code){
135-
int motorPin = -1;
136-
int launchPin= -1;
137-
#ifdef MOTOR_PIN
138-
motorPin = MOTOR_PIN;
139-
#endif
140-
#ifdef LAUNCH_LED_PIN
141-
launchPin = LAUNCH_LED_PIN;
142-
#endif
143-
if(motorPin == -1 && launchPin == -1) return;
144102
for(int i=0; i < code; i++){
145-
if(motorPin >= 0) digitalWrite(motorPin, HIGH);
146-
if(launchPin >= 0) digitalWrite(launchPin, HIGH);
147-
delay(800);
148-
if(motorPin >= 0) digitalWrite(motorPin, LOW);
149-
if(launchPin >= 0) digitalWrite(launchPin, LOW);
150-
delay(400);
103+
motorOn();
104+
launchLedOn();
105+
motorOff(800);
106+
launchLedOff(0, 400);
151107
}
152108
}
153109

154110
void playAudio(){
155111
#ifdef I2S_DOUT
156-
AudioFileSourcePROGMEM* file = new AudioFileSourcePROGMEM(launchAudio, sizeof(launchAudio));
157-
AudioGeneratorWAV* wav = new AudioGeneratorWAV();
158-
wav->begin(file, out);
159-
delay(50); //No delay, no sound
160-
if (wav->isRunning()) {
161-
if (!wav->loop()) wav->stop();
162-
}
112+
AudioFileSourceLittleFS* file = new AudioFileSourceLittleFS(launchAudio);
113+
AudioGeneratorMP3* mp3 = new AudioGeneratorMP3();
114+
mp3->begin(file, out);
115+
while(mp3->loop()){}
116+
mp3->stop();
163117
delete file;
164-
delete wav;
118+
delete mp3;
119+
#else
120+
delay(1000);
165121
#endif
166122
}
123+
124+
bool sendTapTo(String& gamePath){
125+
if(!wifiEnabled) return true;
126+
int code = client.launch(gamePath);
127+
if(code > 0){
128+
expressError(code);
129+
}
130+
return code == 0;
131+
}
132+
133+
void initWiFi() {
134+
WiFi.mode(WIFI_STA);
135+
WiFi.begin(ssid, password);
136+
Serial.print("Connecting to WiFi ..");
137+
while (WiFi.status() != WL_CONNECTED) {
138+
Serial.print('.');
139+
wifiLedOn();
140+
delay(500);
141+
wifiLedOff();
142+
}
143+
wifiEnabled =true;
144+
Serial.println(WiFi.localIP());
145+
wifiLedOn();
146+
motorOn();
147+
motorOff(250);
148+
motorOn(100);
149+
motorOff(250);
150+
}
151+
152+
void loop(void) {
153+
if (nfc.tagPresent()) {
154+
NfcTag tag = nfc.read();
155+
if(tag.hasNdefMessage()){
156+
NdefMessage message = tag.getNdefMessage();
157+
int recordCount = message.getRecordCount();
158+
NdefRecord record = message.getRecord(0);
159+
int payloadLength = record.getPayloadLength();
160+
const byte *payload = record.getPayload();
161+
String payloadAsString = "";
162+
for (int i = 3; i < payloadLength; i++) {
163+
payloadAsString += (char)payload[i];
164+
}
165+
if(!payloadAsString.equalsIgnoreCase("")){
166+
if(sendTapTo(payloadAsString)){
167+
Serial.print("SCAN\t" + payloadAsString + "\n");
168+
Serial.flush();
169+
launchLedOn(0);
170+
motorOn(0);
171+
playAudio();
172+
motorOff(0);
173+
launchLedOff();
174+
}
175+
nfc.haltTag();
176+
delay(1000);
177+
}
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)