openHABster 🐹

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

image
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… :hamster:

STEP 6 - RESULTS

image

25 Likes

Love it!
Now you just have to automate the feeding, based on average energy expended by the Hamster :wink:

Haha - that is AWESOME!! And, as @sl92656 suggests, you need to make the feeder next…Check this out for inspiration:

1 Like

and put a generator in to power your OH server with it … perpetuum mobile :smile:
Ok, on 2nd thought there’s a couple of single points of failure :wink:

2 Likes

That’s a hamsterator then?

I have though about it.
First we’ll add the temperate sensor
I am doing this with my daughter, the critter’s owner.

The problem with hamsters are that they gnaw on everything and are renowned escape artists.
So a feeder would have to be inside the cage and that means shielding any wires with stainless steel tubing…

Well then, make it interactive with your daughter. Based on activity send her text messages like: “Phew, just ran a mile, how about a drink for me?” or “Girl, I am hungry, just jolted down to the store to get a snack and it was closed…”

4 Likes

Good idea, that will be a surprise!!

This is really awesome, Vincent. :+1:
I like all kinds of “strange” meassuring people can think of, But a hamster wheel… I would never have thought about that.

Well the idea was to see how much the hamster runs. Amazing distances really for such small creatures!!
In the back on my head this was also a kind of hamster health monitor. If the hamster doesn’t run, there is something wrong. And a few days after publishing, the running stopped. The hamster passed away… :skull_and_crossbones::black_heart:

1 Like

It´s always sad to loose a pet. I hate it myself, which is why I havnt got any pets anymore. :cry:
As far as I know, hamsters doesnt live long, (max 2-3 years, some just 1 year). He could have been an oldie. Better get a new one for your daughter (and your pleasure monitoring).

Great project. Fun to do with the kids.

I’d have been debugging the code for a while before posting at which point you or Rich would be have chimed in “have you checked the hamster?”

Shame about the death though :sleepy: