I had a bit of time over the festive period and because I go a Raspberry for XMas that freed up an old WEMOS D1 (The old type with the Arduino UNO foot print)
So I got busy:
I had:
- WEMOS D1 R2 (Retired) (The WEMOS D1 or any nodeMCU board should do)
- one 10k Resistor
- one reed sensor
- two magnets
- a hamster wheel
- a hamster cage
- a hamster (not included)
STEP 1 - CAGE PREPARATION
Glue or attach the two magnets at the back of the wheel
I had to sand them so that they didnāt touch the cage
STEP 2 - ELECTRONICS
Attach or solder on the 10k resistor between GND and D2 on the WEMOS
Attach or solder the reed switch between D2 and 5V on the WEMOS
This image is for an arduino but the WEMOS D1 R2 has the same foot print
And I have used the same pins.
Attach the reed sensor to the cage under the axis of the wheel
STEP 3 - CODE
All credit goes to:
Dage Kimber @ https://github.com/Kimbsy/IsMartinRunning
Upload the code to the WEMOS:
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#define LED_PIN 2
#define MAGNET_PIN 16
// make sure this CIRCUMFERENCE constant is the circumference of your wheel (in metres)
#define CIRCUMFERENCE 0.503
const char* ssid = "YOURSSID";
const char* password = "YOURPASSWORD";
const char* mqtt_server = "192.168.0.2"; //Insert your MQTT broker IP here
//Static IP address configuration
IPAddress staticIP(192, 168, 0, 82); //ESP static ip
IPAddress gateway(192, 168, 0, 1); //IP Address of your WiFi Router (Gateway)
IPAddress subnet(255, 255, 255, 0); //Subnet mask
IPAddress dns(192, 168, 0, 12); //DNS
const char* deviceName = "hamster";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
const char* outTopic = "Misc/Hamster/Stat";
const char* outTopicRotation = "Misc/Hamster/Stat/Rotation";
const char* outTopicSpeed = "Misc/Hamster/Stat/Speed";
const char* outTopicDistance = "Misc/Hamster/Stat/Distance";
int magnetState;
int previousMagnetState;
int lastTime = 0; // last time magnetic field was sensed
int totalTime = 0; // total time during this run
int timeChecks = 0; // how many times magnetic field has been detected this run
int timeDiff = 0; // time (in miliseconds) since last detection
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
WiFi.hostname(deviceName);
WiFi.config(staticIP, subnet, gateway, dns);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
for(int i = 0; i<500; i++){
delay(1);
}
}
digitalWrite(LED_PIN, LOW);
delay(500);
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
digitalWrite(LED_PIN, HIGH);
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
if (client.connect("ESP8266Client", "username", "password")) {
client.publish(outTopic, "Sonoff1 booted");
String ipaddress = WiFi.localIP().toString();
char ipchar[ipaddress.length()+1];
ipaddress.toCharArray(ipchar,ipaddress.length()+1);
client.publish(outTopic, ipchar);
} else {
// Wait 5 seconds before retrying
for(int i = 0; i<5000; i++){
delay(1);
}
}
}
}
void setup() {
pinMode(MAGNET_PIN, INPUT); // Initialize the relay pin as an output
pinMode(LED_PIN, OUTPUT);
magnetState = digitalRead(MAGNET_PIN);
previousMagnetState = digitalRead(MAGNET_PIN);
setup_wifi(); // Connect to wifi
client.setServer(mqtt_server, 1883);
}
void flash()
{
digitalWrite(LED_PIN, LOW);
delay(100);
digitalWrite(LED_PIN, HIGH);
}
void calcTime()
{
timeDiff = millis() - lastTime;
lastTime = millis();
if (timeDiff < 2000)
{
totalTime += timeDiff;
timeChecks++;
}
}
void checkRunning()
{
int raw = digitalRead(MAGNET_PIN);
if (raw != magnetState) {
magnetState = raw;
// do timing measurements
calcTime();
flash();
}
}
void checkOutput()
{
// if it's been over 2 seconds since last detection and there has been at least 4 detections, output results of this run
if (((int)millis() - lastTime) > 2000)
{
if (timeChecks >= 4)
{
// calculate info to send out
double averageRotationTime = (totalTime / timeChecks) * 2;
double speed = CIRCUMFERENCE / (averageRotationTime / 1000);
double distance = (CIRCUMFERENCE * timeChecks) / 2;
// send it
client.publish(outTopicRotation, String(averageRotationTime).c_str());
client.publish(outTopicSpeed, String(speed).c_str());
client.publish(outTopicDistance, String(distance).c_str());
// reset variables
totalTime = 0;
timeChecks = 0;
lastTime = millis();
}
else
{
// just reset variables
totalTime = 0;
timeChecks = 0;
lastTime = millis();
}
}
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
// try to detect change in magnetic field
checkRunning();
// determine whether or not to send output
checkOutput();
}
STEP 4 - OPENHAB
Items
Group Hamster
Group HamsterPersist
Number:Speed Hamster_LastSpeed_ms "Hamster Last Speed [%.1f m/s]" (HamsterPersist, House, Hamster) { mqtt="<[mybroker:Misc/Hamster/Stat/Speed:state:default]" }
Number:Length Hamster_LastDistance "Hamster Last Distance [%.1f m]" (HamsterPersist, House, Hamster) { mqtt="<[mybroker:Misc/Hamster/Stat/Distance:state:default]" }
Number:Speed Hamster_AverageSpeed_Today "Hamster Average Speed Today [%.1f m/s]" (House, Hamster)
Number:Length Hamster_TotalDistance_Today "Hamster Total Distance Today [%.1f m]" (House, Hamster)
Number:Speed Hamster_AverageSpeed_Week "Hamster Average Speed This Week [%.1f m/s]" (HamsterPersist, House, Hamster)
Number:Length Hamster_TotalDistance_Week "Hamster Total Distance This Week [%.1f m]" (HamsterPersist, House, Hamster)
Number:Speed Hamster_AverageSpeed_Month "Hamster Average Speed This Month [%.1f m/s]" (HamsterPersist, House, Hamster)
Number:Length Hamster_TotalDistance_Month "Hamster Total Distance This Month [%.1f m]" (HamsterPersist, House, Hamster)
I know, I knowā¦ I have used the MQTT1 binding. Iāll update the post when I get around to complete the migration to MQTTv2, canāt do everything you knowā¦
Rules
rule "Last Run"
when
Item Hamster_LastDistance changed or
Item Hamster_LastSpeed_ms changed
then
if (now.getSecondOfDay >= 10) { // ensures resets have been processed
Hamster_AverageSpeed_Today.postUpdate(Hamster_LastSpeed_ms.averageSince(now.withTimeAtStartOfDay) as Number)
Hamster_TotalDistance_Today.postUpdate(Hamster_LastDistance.sumSince(now.withTimeAtStartOfDay) as Number)
}
end
rule "Save Daily Totals"
when
Time cron "55 59 23 ? * * *" // 5 seconds before midnight
then
Hamster_AverageSpeed_Week.postUpdate(Hamster_LastSpeed_ms.averageSince(now.withTimeAtStartOfDay.withDayOfWeek(1)) as Number)
Hamster_TotalDistance_Week.postUpdate(Hamster_LastDistance.sumSince(now.withTimeAtStartOfDay.withDayOfWeek(1)) as Number)
Hamster_AverageSpeed_Month.postUpdate(Hamster_LastSpeed_ms.averageSince(now.withTimeAtStartOfDay.withDayOfMonth(1)) as Number)
Hamster_TotalDistance_Month.postUpdate(Hamster_LastDistance.sumSince(now.withTimeAtStartOfDay.withDayOfMonth(1)) as Number)
Hamster_AverageSpeed_Today.persist
Hamster_TotalDistance_Today.persist
end
rule "Reset Values"
when
Time cron "5 0 0 ? * * *" // 5 seconds after midnight
then
Hamster_AverageSpeed_Today.postUpdate(0)
Hamster_TotalDistance_Today.postUpdate(0)
end
Persistence (I use influx)
Strategies {
everyHalfHour : "0 0,30 * * * ?"
}
Items {
Persist* : strategy = everyChange, everyHalfHour
HamsterPersist* : strategy = everyUpdate
}
Sitemap
Frame {
Text item=Hamster label="Hamster" icon="hamster" {
Text item=Hamster_LastSpeed_ms
Text item=Hamster_LastDistance
Text item=Hamster_AverageSpeed_Today
Text item=Hamster_TotalDistance_Today label="Hamster Total Distance Today [%.1f km]"
Text item=Hamster_TotalDistance_Week label="Hamster Total Distance This Week [%.1f km]"
Text item=Hamster_TotalDistance_Month label="Hamster Total Distance This Month [%.1f km]"
}
}
STEP 5
Get the hamster runningā¦