433Mhz trip wire sensor system: Difference between revisions

From Pessin randon wiki
Line 478: Line 478:
|}
|}


=== Vastaanottimen lähdekoodi ===
=== Lähettimen lähdekoodi ===
<syntaxhighlight lang="bash" line="1">
<syntaxhighlight lang="bash" line="1">
#include <RH_ASK.h>
#include <RH_ASK.h>

Revision as of 17:30, 24 September 2025

Vastaanotinyksikkö

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.

Vastaanottimen oled näyttö

Mitä näyttö kertoo?

Näyttö on jaettu kahteen osaan:

  • Vasemmassa reunassa (suurena):
    • ID-numero (esim. 5)
    • Huutomerkki ! näkyy, jos koodi on aktiivinen (viesti saatu viimeisen 5 minuutin aikana)
  • Oikeassa reunassa (pienellä tekstillä):
  1. Ylärivi: Esim. 2/(3)/[5]
    • 2 = aktiivisten lähetysten määrä
    • (3) = passiivisten lähetysten määrä (viesti yli 5 min vanha)
    • [5] = kaikkien vastaanotettujen lähetyksien 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. 0100)

Vastaanottimen kytkentäkaavio ja tarvittavat osat

Tarvittavat osat

Komponentti Määrä Hinta Linkki
Arduino Nano v3 1 4,2 € Binghe Development Board with Chip CH340 Type-C Connector 5V 16M Microcontroller Compatible with Arduino IDE Pack of 5
433 MHz ASK-radiovastaanotin 1 2,8 € ALAMSCN Set of 5 433 MHz RF Receiver and Radio Transmission Module + RF 433 MHz Spring Antenna Kit Compatible with Arduino
OLED-näyttö (128×32, I2C, SSD1306) 1 4,5 € MakerHawk I2C OLED Display Module I2C SSD1306 Tiny Screen Module 0.91 Inch, in White 128X32 I2C OLED Driver
9V patteriliitin 1 0,8 € HeyNana 5 x Battery Clip 9 Volt Block Battery I Type Clip Snap with 15 cm Connection Cable for 9 V Blocks Plug Connection Cable
9V Paristo 1 1,2 € GutAlkaLi Batteries 9 V Block Battery, Pack of 6, Longlife Power for Smoke Detectors, Fire & Fire Detectors, Microphone 6lr61 6F22 PP3 MN1604
Momenttikytkin (painonappi) (EI PAKOLLINEN) 1 DAOKAI Micro Switch NC+NO Hinge Straight Lever Micro Limit Switch 125V 1A PDT Limit Micro Switch Moment Switch for Arduino (Pack of 20)
Yhteensä

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
  • Wire

Vastaanottimen 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) {
      return;
    }

    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();
}

Lähetinyksikkö

Lähettimen käyttö

Lähetinyksikön pohjassa on DIP kytkinkaavio, josta valitaan lähetettävä ID. ID tulisi olla jokaisessa lähettimessä oma, jotta hälytykset on identifioitavissa.

Lähettimen pohjayksikkö on irtirepäistävä 360 astetta pyörivä pohja joka irrottuaan aktivoi lähetyksen välittömästi.

Lähetinyksikön voi myös laittaa omalla painollaan pohja edellä jollekkin tasaiselle alustalle ja mikäli se kaatuu alkaa lähetys.

Järjestelmä toimii 9v paristolla.

Lähettimen kytkentäkaavio ja tarvittavat osat

Tarvittavat osat

Komponentti Määrä Hinta Linkki
Arduino Nano v3 1 4,2 € Binghe Development Board with Chip CH340 Type-C Connector 5V 16M Microcontroller Compatible with Arduino IDE Pack of 5
433 MHz ASK-radiolähetin 1 2,8 € ALAMSCN Set of 5 433 MHz RF Receiver and Radio Transmission Module + RF 433 MHz Spring Antenna Kit Compatible with Arduino
9V patteriliitin 1 0,8 € HeyNana 5 x Battery Clip 9 Volt Block Battery I Type Clip Snap with 15 cm Connection Cable for 9 V Blocks Plug Connection Cable
9V Paristo 1 1,2 € GutAlkaLi Batteries 9 V Block Battery, Pack of 6, Longlife Power for Smoke Detectors, Fire & Fire Detectors, Microphone 6lr61 6F22 PP3 MN1604
Momenttikytkin (painonappi) 1 0,25 € DAOKAI Micro Switch NC+NO Hinge Straight Lever Micro Limit Switch 125V 1A PDT Limit Micro Switch Moment Switch for Arduino (Pack of 20)
Yhteensä

Kytkentäkaavio

Lähettimen kytkentä
Vastaanottimen pinni Arduino Nano -pinni Selitys
VCC 5V Virransyöttö
GND GND Maa
DATA (OUT) D12 Lähetysdata
Antenni - Antennin optimaalinen pituus ~17cm
Virtalähteen kytkentä pikarullausnappi
Nappi Arduino Nano Selitys
1 D2 Luku (INPUT_PULLUP)
2 GND Painettaessa yhdistää maahan

Lähettimen lähdekoodi

#include <RH_ASK.h>
#include <SPI.h>

RH_ASK driver(1000);
#define TX_PIN 12

#define DIP1 2
#define DIP2 3
#define DIP3 4
#define DIP4 5

byte crc8_dallas(const String &data) {
  byte crc = 0x00;
  for (int i = 0; i < data.length(); i++) {
    crc ^= data[i];
    for (int j = 0; j < 8; j++) {
      crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1);
    }
  }
  return crc;
}

void setup() {
  driver.init();
  pinMode(DIP1, INPUT_PULLUP);
  pinMode(DIP2, INPUT_PULLUP);
  pinMode(DIP3, INPUT_PULLUP);
  pinMode(DIP4, INPUT_PULLUP);
  pinMode(TX_PIN, OUTPUT);
  randomSeed(analogRead(0));
}

String readDipCode() {
  String code = "";
  code += (digitalRead(DIP1) == LOW) ? '1' : '0';
  code += (digitalRead(DIP2) == LOW) ? '1' : '0';
  code += (digitalRead(DIP3) == LOW) ? '1' : '0';
  code += (digitalRead(DIP4) == LOW) ? '1' : '0';
  return code;
}

void loop() {
  String code = readDipCode();
  String core = "S" + code + "E";
  byte crc = crc8_dallas(core);
  String message = core + String(crc, HEX);

  driver.send((uint8_t*)message.c_str(), message.length());
  driver.waitPacketSent();

  delay(random(800, 1800));  // satunnainen lähetysväli
}

ID Dip taulukko

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