433Mhz trip sensor system: Difference between revisions

From Pessin randon wiki
Line 7: Line 7:
* Vasemmassa reunassa (suurena):
* Vasemmassa reunassa (suurena):


* '''ID-numero''' (esim. <code>5</code>)
* [[File:Vastaanotin-oled.jpg|thumb|Vastaanottimen oled näyttö]]'''ID-numero''' (esim. <code>5</code>)
* '''Huutomerkki <code>!</code>''' näkyy, jos koodi on '''aktiivinen''' (eli viesti saatu viimeisen 5 minuutin aikana)
* '''Huutomerkki <code>!</code>''' näkyy, jos koodi on '''aktiivinen''' (eli viesti saatu viimeisen 5 minuutin aikana)


Line 296: Line 296:


     int dipId = getDipId(dip);
     int dipId = getDipId(dip);
     /*
      
     if (dipId == 0) {
     if (dipId <= 0) {
       Serial.print(F("⚠️ Tuntematon DIP: "));
       //Serial.print(F("⚠️ Tuntematon DIP: "));
       Serial.println(dip);
       //Serial.println(dip);
       return;
       return;
     }
     }
      
     /*
     Serial.print(F("✅ DIP "));
     Serial.print(F("✅ DIP "));
     Serial.print(dip);
     Serial.print(dip);
Line 331: Line 331:
   int passiveCount = 0;
   int passiveCount = 0;


  // Kerätään vastaanotetut DIP:t ja lasketaan aktiiviset/passiiviset
   for (int i = 0; i < 16; i++) {
   for (int i = 0; i < 16; i++) {
     if (dipSeen[i] != 0) {
     if (dipSeen[i] != 0) {
       seenCount++;
       allSeenIndices[seenCount++] = i;
       unsigned long ageMin = (now - dipSeen[i]) / 60000UL;
       unsigned long ageMin = (now - dipSeen[i]) / 60000UL;
       if (ageMin <= 5) {
       if (ageMin <= 5) {
Line 340: Line 341:
         passiveCount++;
         passiveCount++;
       }
       }
      allSeenIndices[seenCount - 1] = i;
     }
     }
   }
   }


   if (seenCount > 0 && (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed)) {
  display.clearDisplay();
 
   if (seenCount <= 0) {
    // Jos ei ole aktiivisia koodeja
    currentActiveIndex = -1;
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println(F("Kuunnellaan radiota..."));
 
    // Vilkku aina näkyviin
    if (now - lastBlink >= BLINK_INTERVAL) {
      blinkState = !blinkState;
      lastBlink = now;
    }
    if (blinkState) {
      display.fillRect(SCREEN_WIDTH - 3, SCREEN_HEIGHT - 3, 2, 2, SSD1306_WHITE);
    }
 
    display.display();
    return;
  }
 
  // Päivitä ruutu vain tarvittaessa
  if (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed) {
     currentActiveIndex = (currentActiveIndex + 1) % seenCount;
     currentActiveIndex = (currentActiveIndex + 1) % seenCount;
     lastDisplaySwitch = now;
     lastDisplaySwitch = now;
Line 350: Line 373:
   }
   }


   display.clearDisplay();
   // Haetaan näytettävä DIP
  int dipIndex = allSeenIndices[currentActiveIndex];
  int id = dipIndex + 1;


   if (seenCount == 0) {
   unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL;
    display.setTextSize(1);
  bool isPassive = (ageMin > 5);
    display.setCursor(0, 0);
    display.println(F("Kuunnellaan radiota..."));
  } else {
    int dipIndex = allSeenIndices[currentActiveIndex];
    int id = dipIndex + 1;


    unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL;
  // Vasemmalle iso ID-numero
     bool isPassive = (ageMin > 5);
  display.setTextSize(4);
  display.setCursor(0, 0);
  display.print(id);
  if (!isPassive) {
     display.print("!");
  }


    // ISO ID vasemmalle
  // Oikealle tilastorivi
    display.setTextSize(4);
  display.setTextSize(1);
    display.setCursor(0, 0);
  int xTextStart = SCREEN_WIDTH - 6 * 10;
    display.print(id);
  char indexStr[20];
    if (!isPassive) display.print("!");
  snprintf(indexStr, sizeof(indexStr), "%d/(%d)/[%d]", activeCount, passiveCount, seenCount);
  display.setCursor(xTextStart, 0);
  display.print(indexStr);


    // Ruutuindeksi
  // PASSIVE-merkinnät jos yli 5 min vanha
    display.setTextSize(1);
  if (isPassive) {
    int xTextStart = SCREEN_WIDTH - 6 * 10;
     char timeStr[10];
     char indexStr[20];
     snprintf(timeStr, sizeof(timeStr), "%lum", ageMin);
     snprintf(indexStr, sizeof(indexStr), "%d/(%d)/[%d]", activeCount, passiveCount, seenCount);
     display.setCursor(xTextStart, 8);
     display.setCursor(xTextStart, 0);
     display.print(timeStr);
     display.print(indexStr);


     // PASSIVE-info jos tarpeen
     display.setCursor(xTextStart, 16);
    if (isPassive) {
    display.print(F("PASSIVE"));
      char timeStr[10];
  }
      snprintf(timeStr, sizeof(timeStr), "%lum", ageMin);
      display.setCursor(xTextStart, 8);
      display.print(timeStr);
      display.setCursor(xTextStart, 16);
      display.print(F("PASSIVE"));
    }


    // DIP-koodi alariville
  // DIP-koodi alariville
    display.setCursor(xTextStart, SCREEN_HEIGHT - 8);
  display.setCursor(xTextStart, SCREEN_HEIGHT - 8);
    display.print(dipIdMap[dipIndex]);
  display.print(dipIdMap[dipIndex]);
  }


   // YHTENÄINEN vilkkupiste – toimii aina
   // Vilkku (aina näkyvissä)
   if (now - lastBlink >= BLINK_INTERVAL) {
   if (now - lastBlink >= BLINK_INTERVAL) {
     blinkState = !blinkState;
     blinkState = !blinkState;
Line 403: Line 422:
   display.display();
   display.display();
}
}


</syntaxhighlight>
</syntaxhighlight>

Revision as of 22:13, 19 June 2025

Vastaanottimen käyttö

Tämä laite vastaanottaa 433 MHz -radioviestejä, joissa on kiinteitä DIP-koodeja. Jokaisella DIP-koodilla on pysyvä ID-numero (1–16). Näytöllä näkyy aina yksi koodi kerrallaan.

Mitä näyttö kertoo?

Näyttö on jaettu kahteen osaan:

  • Vasemmassa reunassa (suurena):
  • Vastaanottimen oled näyttö
    ID-numero (esim. 5)
  • Huutomerkki ! näkyy, jos koodi on aktiivinen (eli viesti saatu viimeisen 5 minuutin aikana)

🔹 Oikeassa reunassa (pienellä tekstillä):

  1. Ylärivi: Esim. 2 / (3) / [5]
    • 2 = aktiivisten määrä
    • (3) = passiivisten määrä (viesti yli 5 min vanha)
    • [5] = kaikkien vastaanotettujen määrä
  2. Toinen rivi (vain jos koodi on passiivinen):
    • Aika viimeisestä viestistä minuutteina (esim. 17m)
  3. Kolmas rivi (vain jos passiivinen):
    • Sana PASSIVE
  4. Alarivi:
    • DIP-koodi (esim. 0101)

ID Dip taulukko

ID DIP-koodi
1 0000
2 0001
3 0010
4 0011
5 0100
6 0101
7 0110
8 0111
9 1000
10 1001
11 1010
12 1011
13 1100
14 1101
15 1110
16 1111

Vastaanottimen kytkentäkaavio ja tarvittavat osat

Tarvittavat osat:

Komponentti Määrä
Arduino Nano v3 1
433 MHz ASK-radiovastaanotin 1
OLED-näyttö (128×32, I2C, SSD1306) 1
Momenttikytkin (painonappi) 1

Näytön kytkentä

OLED-pinni Arduino Nano -pinni Selitys
VCC 5V Virransyöttö
GND GND Maa
SDA A4 I2C-data
SCL A5 I2C-kello

Vastaanottimen kytkentä

Vastaanottimen pinni Arduino Nano -pinni Selitys
VCC 5V Virransyöttö
GND GND Maa
DATA (OUT) D12 Vastaanottodata

Näytön pikarullausnappi

Nappi Arduino Nano Selitys
1 D2 Luku (INPUT_PULLUP)
2 GND Painettaessa yhdistää maahan

Lisää tarvittavat kirjastot

  • RadioHead (RH_ASK)
  • Adafruit SSD1306
  • Adafruit GFX

Lähettimen lähdekoodi

#include <RH_ASK.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Radiovastaanotin
RH_ASK driver(1000, 12, 11, 10); // RX = D12

// Ajastukset
const unsigned long ENTRY_TIMEOUT = 30 * 60 * 1000UL; // 30 min
const unsigned long DISPLAY_DURATION = 2500;         // 2.5 s

// Seuraava ruutu nappi
#define BUTTON_PIN 2

// Kiinteät DIP-koodit (16 kpl)
const char* dipIdMap[16] = {
  "0000", "0001", "0010", "0011",
  "0100", "0101", "0110", "0111",
  "1000", "1001", "1010", "1011",
  "1100", "1101", "1110", "1111"
};

//Vilkku
bool blinkState = false;
unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 500;


// Tallennetaan viimeisin aikaleima DIP-koodeille
unsigned long dipSeen[16] = {0};

// Painallusmuuttujat
bool buttonPressed = false;
unsigned long lastDisplaySwitch = 0;

// CRC8 Dallas/Maxim
byte crc8_dallas(const char* data, uint8_t len) {
  byte crc = 0x00;
  for (uint8_t i = 0; i < len; i++) {
    crc ^= data[i];
    for (uint8_t j = 0; j < 8; j++) {
      crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
    }
  }
  return crc;
}

// Palauttaa ID:n 1–16 tai 0 jos tuntematon
int getDipId(const char* dip) {
  for (int i = 0; i < 16; i++) {
    if (strncmp(dipIdMap[i], dip, 4) == 0) {
      return i + 1;
    }
  }
  return 0;
}

void setup() {
  // Serial.begin(9600);
  // delay(1000);

  pinMode(BUTTON_PIN, INPUT_PULLUP);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println(F("OLED ei vastaa"));
    display.display();
    while (1); // Pysäyttää ohjelman
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("Odotetaan..."));
  display.display();

  
  if (!driver.init()) {
    display.clearDisplay();
    display.setCursor(0, 0);
    display.println(F("Radio ei vastaa"));
    display.display();
    while (1); // 
  }
  

  pinMode(12, INPUT);
}

void loop() {
  receiveRadio();
  cleanExpiredEntries();
  checkButton();
  updateDisplay();
  delay(10);
}

void checkButton() {
  static bool prevState = HIGH;
  bool state = digitalRead(BUTTON_PIN);

  if (prevState == HIGH && state == LOW) {
    buttonPressed = true;
  }

  prevState = state;
}

void receiveRadio() {
  uint8_t buf[32];
  uint8_t buflen = sizeof(buf);

  if (driver.recv(buf, &buflen)) {
    if (buflen < 8 || buf[0] != 'S' || buf[5] != 'E') return;

    byte expectedCrc = crc8_dallas((char*)buf, 6);
    char crcStr[3] = { (char)buf[6], (char)buf[7], '\0' };
    byte receivedCrc = strtoul(crcStr, NULL, 16);
    if (expectedCrc != receivedCrc) return;

    char dip[5];
    memcpy(dip, &buf[1], 4);
    dip[4] = '\0';

    int dipId = getDipId(dip);
    
    if (dipId <= 0) {
      //Serial.print(F("⚠️ Tuntematon DIP: "));
      //Serial.println(dip);
      return;
    }
    /*
    Serial.print(F("✅ DIP "));
    Serial.print(dip);
    Serial.print(F(" → ID "));
    Serial.println(dipId);
    */

    dipSeen[dipId - 1] = millis(); // Päivitä aikaleima
  }
}

void cleanExpiredEntries() {
  unsigned long now = millis();
  for (int i = 0; i < 16; i++) {
    if (dipSeen[i] != 0 && now - dipSeen[i] > ENTRY_TIMEOUT) {
      dipSeen[i] = 0; // Nollaa aikaleima = ei enää aktiivinen
    }
  }
}

void updateDisplay() {
  static int currentActiveIndex = -1;
  unsigned long now = millis();

  int allSeenIndices[16];
  int seenCount = 0;
  int activeCount = 0;
  int passiveCount = 0;

  // Kerätään vastaanotetut DIP:t ja lasketaan aktiiviset/passiiviset
  for (int i = 0; i < 16; i++) {
    if (dipSeen[i] != 0) {
      allSeenIndices[seenCount++] = i;
      unsigned long ageMin = (now - dipSeen[i]) / 60000UL;
      if (ageMin <= 5) {
        activeCount++;
      } else {
        passiveCount++;
      }
    }
  }

  display.clearDisplay();

  if (seenCount <= 0) {
    // Jos ei ole aktiivisia koodeja
    currentActiveIndex = -1;
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println(F("Kuunnellaan radiota..."));

    // Vilkku aina näkyviin
    if (now - lastBlink >= BLINK_INTERVAL) {
      blinkState = !blinkState;
      lastBlink = now;
    }
    if (blinkState) {
      display.fillRect(SCREEN_WIDTH - 3, SCREEN_HEIGHT - 3, 2, 2, SSD1306_WHITE);
    }

    display.display();
    return;
  }

  // Päivitä ruutu vain tarvittaessa
  if (now - lastDisplaySwitch >= DISPLAY_DURATION || buttonPressed) {
    currentActiveIndex = (currentActiveIndex + 1) % seenCount;
    lastDisplaySwitch = now;
    buttonPressed = false;
  }

  // Haetaan näytettävä DIP
  int dipIndex = allSeenIndices[currentActiveIndex];
  int id = dipIndex + 1;

  unsigned long ageMin = (now - dipSeen[dipIndex]) / 60000UL;
  bool isPassive = (ageMin > 5);

  // Vasemmalle iso ID-numero
  display.setTextSize(4);
  display.setCursor(0, 0);
  display.print(id);
  if (!isPassive) {
    display.print("!");
  }

  // Oikealle tilastorivi
  display.setTextSize(1);
  int xTextStart = SCREEN_WIDTH - 6 * 10;
  char indexStr[20];
  snprintf(indexStr, sizeof(indexStr), "%d/(%d)/[%d]", activeCount, passiveCount, seenCount);
  display.setCursor(xTextStart, 0);
  display.print(indexStr);

  // PASSIVE-merkinnät jos yli 5 min vanha
  if (isPassive) {
    char timeStr[10];
    snprintf(timeStr, sizeof(timeStr), "%lum", ageMin);
    display.setCursor(xTextStart, 8);
    display.print(timeStr);

    display.setCursor(xTextStart, 16);
    display.print(F("PASSIVE"));
  }

  // DIP-koodi alariville
  display.setCursor(xTextStart, SCREEN_HEIGHT - 8);
  display.print(dipIdMap[dipIndex]);

  // Vilkku (aina näkyvissä)
  if (now - lastBlink >= BLINK_INTERVAL) {
    blinkState = !blinkState;
    lastBlink = now;
  }
  if (blinkState) {
    display.fillRect(SCREEN_WIDTH - 3, SCREEN_HEIGHT - 3, 2, 2, SSD1306_WHITE);
  }

  display.display();
}