Rotellino
Rotellino is a DIY bike light.
Rotellino works using the variation of the magnetic field, generated by the movement of the bike wheel rim, to rotate a rotor made up of 6 magnets and generate electricity.
Once working, the goal is to monitor the voltage and electricity data arriving from the sensors mounted on the rotellino with an app (blynk) on a mobile phone
Supplies
- 2 plastic bearings 623 3x10x4mm
- 6 N52 magnets 6x3 mm
- 2 M3 20 mm screws + nuts
- 2 x 5mm bright white LEDs
- Voltage and electricity sensors
- phone and google sheets for data monitoring
- 3d printer
- Drill
- Copper wire
build a coil by wrapping around a support the copper wire
attach the leds to the coil
Downloads
design the rotellino’s body and supports on a 3d software
3d print the component designed
assemble the rotor, made out of six magnets that are placed alternating their polarity
assemble all the parts together
write the arduino code to make the voltage and electricity sensors work and to send the data they collect to a google sheets file
test the functioning and of the rotellino with an oscilloscope
create a google sheets file to collect and analize the data took from the rotellino
connect arduino with google sheets
monitor from remote the data coming from the rotellino
Video of the Rotellino Working
Google Sheets Link:
https://docs.google.com/spreadsheets/d/1g_iSECpZtVPYQIcYqwiuH6AiCyx23LEwCrdgco5Elpg/edit#gid=0
Arduino Code
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <Adafruit_Sensor.h>
#include <time.h>
#include <ESP_Google_Sheet_Client.h>
//For ACS712 current sensor
const int sensorIn = 4;
int mVperAmp = 185; // this the 5A version of the ACS712 -use 100 for 20A Module and 66 for 30A Module
float Voltage, VRMS, AmpsRMS;
//For Hall effect sensor
int hall_pin = 17;
float hall_thresh = 5.0;
float rpm_val, time_passed;
//For Wi-fi connection
unsigned long previousMillis = 0;
unsigned long interval = 30000;
// Google Project ID
#define PROJECT_ID "iot-rotellino"
// Service Account's client email
#define CLIENT_EMAIL "iot-rotellino@iot-rotellino.iam.gserviceaccount.com"
// Service Account's private key
const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDRU3XmSDL8oaiE\nOOu0YUdlCcqMf2UAPj0yh0roocl6VIp/bX+qKD3MHV9fOfHfxTMlcvsEGPtMDR2J\n3Kn7z2DX0s9t5D85qL3yq0FtlZarhrkt/EVFtSGyQ4brbVfRfX3z8XpTSzoA8S7L\nh++wW79ylEl+GlBP7taeSOtXDUOfEorXsu2mOv1pL7L5VJdWcGofwJJuWpjLFnWW\ndhxwPMxchRsCIAQs7ujSuoCkPVDZXXMqTE0OgbZv9qzuZ93uWn47lhuItL5XkO7D\nOFTK/vc1M6MvuXKf7riwxUp96iFcP1M7C6tsgVBoAMRr+sFTCUp+eDvkpV2vnGXO\nck5RkoBxAgMBAAECggEAB2jCzkxDmGphjFbLqlrjWf5A5UrEan/0JAaNXIErSuTq\nKrzXh+zVfyMgkz6WijBp4v0REH5X4BjeFIP6SlCFU4OsVgphszHwrexpVry0sPd3\n34VuWg093kL0LzhXMZWVfsh52QU+6qCe1HbksxUwuhh3tNAE2WPmqBESkv8uzmVF\n65rDIW5xdbe4Nt31QyhBo3FLomvGSIq6RtcTDiJGB/jtkRP3Ugs7P1Ps0wlpTcux\ngWAzNjDIkpy4dzpXsFK02qCI8QibFQia67C967EQPGJmoTUIwLQPZKaHan1d7Dpe\neAoEol+8jI859piPsE4/ZuvxseYFuo3Sa0xcxwAhdwKBgQD37QvDMZEIR5V6uSJY\nIWIWS9w2il28T+zqS8cBqVVRrJD2AL1VLAuFLMgw4vGw0MFAjufeystmpWSNSzN3\nA/awCnAyq9tivDZZTJALsIM+3llXJwxgYhAtlkGwwE72eQnJmEFGmmYqvYKbExL6\nfDvbI00GtFsDTtc7aCXa5ro09wKBgQDYJJuL0bZMiqZMlwqSOHZ15nbIhsUPEFDr\nRW8T7I4h4IvgXK9LqWfGRWg5fdDssPScPI2+eFdZpjGgEb5JAjhqSYsYIAokRw5x\nHF9oBfbuOWo6E5bso3fpeSYHKirO5MVfrehUrW0e+Z0Ylhoyg5/CuBUsHq7IgDK/\nfHY4Gk/j1wKBgHpjKktRTKcpr0DF445d7G3VRQAnjd5IFkwS3EqVrOiEp4rJEq3Y\n8FbtpGV9opIGe1/DK/NvaLljLCAT33QBIOYGQRzCeapj/vBWO0WJ/UArwy6iuBlc\nT2AxrHv0cwZ4+bvqzU5tKcIviynCYLwGWAX1hzCoF8WqRdWttAI7o/BBAoGAKWPp\nPX8tT78FVYlfBt01IiK+AGx+dAIF3Ofw+3nDRg1/+7kEAJMyQi+sY8YKKilAzmJy\nKlVVNN+0hRigvc5lC0WGE1qfVo8c3uA2DO+Hd9sa0oBJ2Ir9PYJrm9ehVvlMKqRc\n50pGqTXXtYuY/K9j+p/Rvh8qDU8vaKfm45t2TQUCgYEAwxL3f2g9V2tdyIQXN/Bj\nX9lnQtUGBvXMo6D4vqKOVejEOjfK2DVvuVJEGtbDmwJ9ljQGklybSJ19ohEujdpV\n84NVY9LiZGya9F6SaZadOPKqAvYgFwmWRfOsdiBIXlJ9FXodcd4oE4q1AFlX3Ogq\n+YyMY1xY4spSn2RxLJc8AfI=\n-----END PRIVATE KEY-----\n";
// The ID of the spreadsheet where you'll publish the data
const char spreadsheetId[] = "1g_iSECpZtVPYQIcYqwiuH6AiCyx23LEwCrdgco5Elpg";
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 3000;
// Token Callback function
void tokenStatusCallback(TokenInfo info);
// NTP server to request epoch time
const char* ntpServer = "pool.ntp.org";
// Variable to save current epoch time
unsigned long epochTime;
// Function that gets current epoch time
unsigned long getTime() {
time_t now;
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
//Serial.println("Failed to obtain time");
return (0);
}
time(&now);
return now;
}
void setup() {
Serial.begin(115200);
configTime(0, 0, ntpServer); //Configure time
GSheet.printf("ESP Google Sheet Client v%s\n\n", ESP_GOOGLE_SHEET_CLIENT_VERSION);
//Wi-fi connection
WiFi.mode(WIFI_STA);
WiFiManager wm;
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
res = wm.autoConnect("AlternatoreIOT"); // anonymous ap
//res = wm.autoConnect("AutoConnectAP", "password"); // password protected ap
pinMode(LED_BUILTIN, OUTPUT);
if (!res) {
Serial.println("Failed to connect");
// ESP.restart();
}
else {
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
}
//Hall sensor
pinMode(hall_pin, INPUT);
//ACS712 sensor
pinMode(sensorIn, INPUT);
// Set the callback for Google API access token generation status (for debug only)
GSheet.setTokenCallback(tokenStatusCallback);
// Set the seconds to refresh the auth token before expire (60 to 3540, default is 300 seconds)
GSheet.setPrerefreshSeconds(10 * 60);
// Begin the access token generation for Google API authentication
GSheet.begin(CLIENT_EMAIL, PROJECT_ID, PRIVATE_KEY);
}
void loop() {
//Wi-fi connection
unsigned long currentMillis = millis();
// if WiFi is down, try reconnecting every CHECK_WIFI_TIME seconds
if ((WiFi.status() != WL_CONNECTED) && (currentMillis - previousMillis >= interval)) {
Serial.print(millis());
Serial.println("Reconnecting to WiFi...");
WiFi.disconnect();
WiFi.reconnect();
previousMillis = currentMillis;
}
else if (WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_BUILTIN, HIGH);
}
else {
digitalWrite(LED_BUILTIN, LOW);
}
// Call ready() repeatedly in loop for authentication checking and processing
bool ready = GSheet.ready();
if (ready && millis() - lastTime > timerDelay) {
lastTime = millis();
FirebaseJson response;
Serial.println("\nAppend spreadsheet values...");
Serial.println("----------------------------");
FirebaseJson valueRange;
//Hall sensor
float hall_count = 1.0;
float start = micros();
bool on_state = false;
// counting number of times the hall sensor is tripped
// but without double counting during the same trip
while (true) {
if (digitalRead(hall_pin) == 0) {
if (on_state == false) {
on_state = true;
hall_count += 1.0;
}
}
else {
on_state = false;
}
if (hall_count >= hall_thresh) {
break;
}
}
float end_time = micros();
time_passed = ((end_time - start) / 1000000.0);
rpm_val = (hall_count / time_passed) * 60.0;
epochTime = getTime();
//ACS712
Voltage = getVPP();
VRMS = (Voltage / 2.0) * 0.707; //root 2 is 0.707
AmpsRMS = ((VRMS * 1000) / mVperAmp) - 0.3; //0.3 is the error I got for my sensor
delay (100);
valueRange.add("majorDimension", "COLUMNS");
valueRange.set("values/[0]/[0]", epochTime);
valueRange.set("values/[1]/[0]", rpm_val);
valueRange.set("values/[2]/[0]", time_passed);
valueRange.set("values/[3]/[0]", AmpsRMS);
valueRange.set("values/[4]/[0]", VRMS);
// For Google Sheet API ref doc, go to https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append
// Append values to the spreadsheet
bool success = GSheet.values.append(&response /* returned response */, spreadsheetId /* spreadsheet Id to append */, "Foglio1!A2" /* range to append */, &valueRange /* data range to append */);
if (success) {
response.toString(Serial, true);
valueRange.clear();
}
else {
Serial.println(GSheet.errorReason());
}
Serial.println();
Serial.println(ESP.getFreeHeap());
}
}
float getVPP() {
float result;
int readValue; // value read from the sensor
int maxValue = 0; // store max value here
int minValue = 4096; // store min value here ESP32 ADC resolution
uint32_t start_time = millis();
while ((millis() - start_time) < 1000) //sample for 1 Sec
{
readValue = analogRead(sensorIn);
// see if you have a new maxValue
if (readValue > maxValue)
{
/*record the maximum sensor value*/
maxValue = readValue;
}
if (readValue < minValue)
{
/*record the minimum sensor value*/
minValue = readValue;
}
}
result = ((maxValue - minValue) * 3.3) / 4096.0; // Subtract min from max - ESP32 ADC resolution 4096
return result;
}
void tokenStatusCallback(TokenInfo info) {
if (info.status == token_status_error) {
GSheet.printf("Token info: type = %s, status = %s\n", GSheet.getTokenType(info).c_str(), GSheet.getTokenStatus(info).c_str());
GSheet.printf("Token error: %s\n", GSheet.getTokenError(info).c_str());
}
else {
GSheet.printf("Token info: type = %s, status = %s\n", GSheet.getTokenType(info).c_str(), GSheet.getTokenStatus(info).c_str());
}
}