433Mhz trip sensor system: Difference between revisions

From Pessin randon wiki
Blanked the page
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
== 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):
* [[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)
=== 🔹 Oikeassa reunassa (pienellä tekstillä): ===
# '''Ylärivi:'''  Esim. <code>2 / (3) / [5]</code>
#* <code>2</code> = aktiivisten määrä
#* <code>(3)</code> = passiivisten määrä (viesti yli 5 min vanha)
#* <code>[5]</code> = kaikkien vastaanotettujen määrä
# '''Toinen rivi (vain jos koodi on passiivinen):'''
#* Aika viimeisestä viestistä minuutteina (esim. <code>17m</code>)
# '''Kolmas rivi (vain jos passiivinen):'''
#* Sana <code>PASSIVE</code>
# '''Alarivi:'''
#* DIP-koodi (esim. <code>0101</code>)
== ID Dip taulukko ==
{| class="wikitable"
!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: ==
{| class="wikitable"
!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ä ====
{| class="wikitable"
!OLED-pinni
!Arduino Nano -pinni
!Selitys
|-
|VCC
|5V
|Virransyöttö
|-
|GND
|GND
|Maa
|-
|SDA
|A4
|I2C-data
|-
|SCL
|A5
|I2C-kello
|}
==== Vastaanottimen kytkentä ====
{| class="wikitable"
!Vastaanottimen pinni
!Arduino Nano -pinni
!Selitys
|-
|VCC
|5V
|Virransyöttö
|-
|GND
|GND
|Maa
|-
|DATA (OUT)
|D12
|Vastaanottodata
|}
==== Näytön pikarullausnappi ====
{| class="wikitable"
!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 ==
<syntaxhighlight lang="c++" line="1">
#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();
}
</syntaxhighlight>

Latest revision as of 19:17, 22 June 2025