המלצה | למי שרוצה שעון שבת חכם למערכת חימום בגז וכד'
-
הקדמה קצרה, מי שמכיר (נראה לי שכולם) את המערכת של חימום מים בגז למקלחת, יודע שזה עובד עם אש, וכידוע אש זה דבר שאסור להשתמש בו בשבת, ואצלי בכל אופן (בישיבה) לא תמיד זוכרים לכבות את המערכת, אז יצרתי שעון שבת עם זמני כניסת ויציאת שבת (מצורף סקריפט להורדה של כמה שתרצו) להלן ההסבר המפורט
מה צריך לקנות:- קונים חלק שנקרא ESP32 - מכשיר חכם עם המון אפשרויות, הנה קישור למוצר כזה באליאקספרס קישור affilate ממליץ על הדגם עם הכרטיס הרחבה יותר נוח.
- קונים חלק שנקרא שעון RTC קישור affilate זה אחראי על השעה ותאריך
- מסך קישור affilate לא נראה לי שצריך להסביר מה הוא עושה
- כפתורים קישור affilate כדי ללחוץ :).
- דרך כלשהיא לחבר את כל זה, אפשר עם לוח מחורר קישור affilate או עם לוח בהתאמה אישית (מצורף קובץ לשליחה לאתרים שמייצרים את זה (משלוח יקר) הקובץ מתאים לESP עם הכרטיס הרחבה.) מחברים (לנוחות) קישור affilate
- קונטקטור קישור affilate זה כדי להעביר חשמל 220V
מה עושים עם כל זה? - מתקינים על המחשב תוכנה שנקראת arduino IDE.
- בסרגל העליון, לחצו על File (קובץ) ואז על Preferences (העדפות).
- בחלון שנפתח, חפשו שורה שנקראת: Additional Boards Manager URLs.
- במלבן הריק לידה, תדביקו את הקישור הבא: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json (אם כבר יש שם קישור אחר, פשוט שימו פסיק ביניהם או תרדו שורה). לחצו על OK.
עכשיו כשהתוכנה יודעת איפה לחפש, צריך להוריד את הקבצים בפועל:
-
בצד שמאל של התוכנה, לחצו על האייקון של Boards Manager (זה נראה כמו לוח קטן עם עיגולים).
-
בשורת החיפוש למעלה, הקלידו: ESP32.
תמצאו את החבילה של חברת Espressif Systems ולחצו על כפתור ה-INSTALL. זה יכול לקחת דקה או שתיים, תנו לזה לסיים.
עכשיו כשהכל מותקן, נבחר את הלוח שלנו כדי שהקוד ייצרב נכון:
- לכו ל-Tools (כלים) -> Board -> esp32.
בחרו בדגם: DOIT ESP32 DEVKIT V1.
חברו את ה-ESP32 למחשב עם כבל USB איכותי (שימו לב שיש כבלים שהם רק לטעינה ולא לנתונים, הם לא יעבדו).
שוב ב-Tools, לכו ל-Port ובחרו את היציאה שנוספה (COM3 או משהו דומה).
התקנת הספריות: - כדי שהקוד שכתבנו יבין מה זה מסך ומה זה שעון, צריך להוריד לו ספריות מוכנות. לחצו על האייקון של ה-Library Manager (המדף מתחת ללוחות) וחפשו והתקינו את אלו:
Adafruit SSD1306
Adafruit GFX Library
RTClib (של Adafruit)
עכשיו לתכנות בפועל:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <RTClib.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); RTC_DS3231 rtc; // כאן מוגדרים הפינים של כל דבר, ניתן לשנות אותם (יש מספרים על הלוח עצמו) const int RELAY_PIN = 5; // זה הממסר (קונטקטור) const int BTN_UP = 15, BTN_DOWN = 4, BTN_SELECT = 16, BTN_BACK = 17; // זה הכפתורים bool isSetupMode = false; int setupStep = 0; int editYear, editMonth, editDay, editHour, editMin; String nextEventStr = "Scanning..."; bool manualOverride = false; bool lastDSTState = false; // כאן כותבים את כל האירועים (כיבוי והדלקה, מצורף סקריפט פייתון להורדת רשימה ארוכה של זמנים) const char* const schedule[] PROGMEM = { "{ 01/01/2000 00:00 on }", }; const int scheduleSize = sizeof(schedule) / sizeof(schedule[0]); // --- אייקונים --- (לא יצא יפה, זה באשמת ג'מיני) const unsigned char hand_icon[] PROGMEM = { 0x18, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x00, 0x3C, 0x00 }; const unsigned char sun_icon[] PROGMEM = { 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0xfe, 0x00, 0x38, 0x00, 0x54, 0x00, 0x10, 0x00 }; const unsigned char rain_icon[] PROGMEM = { 0x10, 0x00, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00 }; bool isIsraelDST(DateTime t) { int y = t.year(); int m = t.month(); int d = t.day(); if (m < 3 || m > 10) return false; if (m > 3 && m < 10) return true; if (m == 3) { int lastFriday = 31 - ((DateTime(y, 3, 31).dayOfTheWeek() + 2) % 7); return (d > lastFriday) || (d == lastFriday && t.hour() >= 2); } if (m == 10) { int lastSunday = 31 - DateTime(y, 10, 31).dayOfTheWeek(); return (d < lastSunday) || (d == lastSunday && t.hour() < 2); } return false; } void updateDST() { DateTime now = rtc.now(); bool currentDST = isIsraelDST(now); if (currentDST != lastDSTState) { if (now.hour() == 2 && now.minute() == 0) { if (currentDST) { rtc.adjust(now + TimeSpan(0, 1, 0, 0)); } else { rtc.adjust(now - TimeSpan(0, 1, 0, 0)); } lastDSTState = currentDST; syncWithSchedule(rtc.now()); display.clearDisplay(); DateTime updated = rtc.now(); display.setTextSize(3); display.setCursor(20, 0); display.printf("%02d:%02d", updated.hour(), updated.minute()); display.setTextSize(1); display.setCursor(35, 30); display.printf("%02d/%02d/%04d", updated.day(), updated.month(), updated.year()); display.display(); Serial.println("DST Adjustment applied!"); } else { lastDSTState = currentDST; } } } void setup() { Serial.begin(115200); pinMode(RELAY_PIN, OUTPUT); pinMode(BTN_UP, INPUT_PULLUP); pinMode(BTN_DOWN, INPUT_PULLUP); pinMode(BTN_SELECT, INPUT_PULLUP); pinMode(BTN_BACK, INPUT_PULLUP); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) Serial.println("OLED Fail"); rtc.begin(); lastDSTState = isIsraelDST(rtc.now()); display.clearDisplay(); display.setTextColor(WHITE); syncWithSchedule(rtc.now()); } void syncWithSchedule(DateTime now) { int lastPassedEvent = -1; bool foundNext = false; for (int i = 0; i < scheduleSize; i++) { char buffer[40]; strcpy_P(buffer, (char*)pgm_read_ptr(&(schedule[i]))); String line = String(buffer); int d = line.substring(2, 4).toInt(); int m = line.substring(5, 7).toInt(); int y = line.substring(8, 12).toInt(); int hr = line.substring(13, 15).toInt(); int mn = line.substring(16, 18).toInt(); DateTime eventTime(y, m, d, hr, mn, 0); if (eventTime.unixtime() <= now.unixtime()) { lastPassedEvent = i; } else if (!foundNext) { nextEventStr = String(d) + "/" + String(m) + " " + String(hr) + ":" + (mn < 10 ? "0" : "") + String(mn); nextEventStr += (line.indexOf("on") != -1) ? " ON" : " OFF"; foundNext = true; } } if (lastPassedEvent != -1 && !manualOverride) { char buffer[40]; strcpy_P(buffer, (char*)pgm_read_ptr(&(schedule[lastPassedEvent]))); String lastLine = String(buffer); digitalWrite(RELAY_PIN, (lastLine.indexOf("on") != -1) ? HIGH : LOW); } if (!foundNext) nextEventStr = "END OF LIST"; static int lastKnownEvent = -1; if (lastPassedEvent != lastKnownEvent) { manualOverride = false; lastKnownEvent = lastPassedEvent; } } void loop() { updateDST(); DateTime now = rtc.now(); if (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW && !isSetupMode) { unsigned long startManual = millis(); while(digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW) { if (millis() - startManual > 1000) break; } if (millis() - startManual > 1000) { if (!manualOverride) { manualOverride = true; digitalWrite(RELAY_PIN, HIGH); } else if (digitalRead(RELAY_PIN) == HIGH) { digitalWrite(RELAY_PIN, LOW); } else { manualOverride = false; syncWithSchedule(now); } display.clearDisplay(); display.setCursor(10, 25); display.setTextSize(2); if (!manualOverride) display.print("AUTO MODE"); else display.print(digitalRead(RELAY_PIN) ? "MANUAL ON" : "MANUAL OFF"); display.display(); while(digitalRead(BTN_UP) == LOW || digitalRead(BTN_DOWN) == LOW); delay(300); } } if (digitalRead(BTN_SELECT) == LOW && !isSetupMode) { unsigned long start = millis(); while(digitalRead(BTN_SELECT) == LOW) if (millis() - start > 2000) break; if (millis() - start > 2000) { isSetupMode = true; editYear = now.year(); editMonth = now.month(); editDay = now.day(); editHour = now.hour(); editMin = now.minute(); setupStep = 0; while(digitalRead(BTN_SELECT) == LOW); delay(200); } } display.clearDisplay(); if (isSetupMode) { runSetupMenu(); } else { if (now.second() == 0 || now.second() % 30 == 0) { syncWithSchedule(now); } display.setTextSize(3); display.setCursor(20, 0); display.printf("%02d:%02d", now.hour(), now.minute()); display.setTextSize(1); display.setCursor(35, 30); display.printf("%02d/%02d/%04d", now.day(), now.month(), now.year()); if (isIsraelDST(now)) { display.drawBitmap(110, 28, sun_icon, 12, 12, WHITE); } else { display.drawBitmap(110, 28, rain_icon, 12, 12, WHITE); } display.drawLine(0, 42, 128, 42, WHITE); display.setCursor(0, 48); display.print("next: "); display.print(nextEventStr); display.setCursor(0, 57); display.print("now: "); display.print(digitalRead(RELAY_PIN) ? "ON" : "OFF"); if (manualOverride) display.drawBitmap(110, 52, hand_icon, 12, 12, WHITE); } display.display(); delay(50); } void runSetupMenu() { display.setTextSize(1); display.setCursor(0,0); display.println("SETUP TIME"); display.setTextSize(2); display.setCursor(0, 20); if(setupStep==0) display.printf("Year: %d", editYear); else if(setupStep==1) display.printf("Month: %d", editMonth); else if(setupStep==2) display.printf("Day: %d", editDay); else if(setupStep==3) display.printf("Hour: %d", editHour); else if(setupStep==4) display.printf("Min: %d", editMin); if (digitalRead(BTN_UP) == LOW) { if(setupStep==0) editYear++; else if(setupStep==1) editMonth=(editMonth % 12) + 1; else if(setupStep==2) editDay=(editDay % 31) + 1; else if(setupStep==3) editHour=(editHour + 1) % 24; else if(setupStep==4) editMin=(editMin + 1) % 60; delay(200); } if (digitalRead(BTN_DOWN) == LOW) { if (setupStep == 0) editYear--; else if (setupStep == 1) { editMonth--; if (editMonth < 1) editMonth = 12; } else if (setupStep == 2) { editDay--; if (editDay < 1) editDay = 31; } else if (setupStep == 3) { editHour--; if (editHour < 0) editHour = 23; } else if (setupStep == 4) { editMin--; if (editMin < 0) editMin = 59; } delay(200); } if (digitalRead(BTN_SELECT) == LOW) { setupStep++; while(digitalRead(BTN_SELECT) == LOW); delay(200); if (setupStep > 4) { rtc.adjust(DateTime(editYear, editMonth, editDay, editHour, editMin, 0)); lastDSTState = isIsraelDST(rtc.now()); isSetupMode = false; manualOverride = false; syncWithSchedule(rtc.now()); } } if (digitalRead(BTN_BACK) == LOW) { isSetupMode = false; while(digitalRead(BTN_BACK) == LOW); delay(200); } }הסבר על הקוד
- המסך מוגדר ברזולוציית 128X64 זה המסך ששמתי בקישור, אם יש לכם מסך אחר תשנו את זה לרזולוציה שלו
- הפינים: RELAY_PIN = 5; זה אומר שהפין שממנו יוצא חשמל כשדלוק זה פין D5. וכן הכפתורים, 15, 4, 16, 17 (הם אחד ליד השני, המספרים סתם קופצים).
- בקוד הזה יש 4 כפתורים, למעלה, למטה, אישור וחזור. לחיצה ארוכה על כפתור אישור מכניסה את המערכת למצב כיוון שעה ותאריך.
לחיצה ארוכה על למעלה ולמטה הופכת את המערכת למצב ידני (דלוק, כבוי) מתאפס כשמשתנה תוכנית. - שעון חורף/קיץ מתחלף אוטומטית
- את הזמנים שמים בשורה 23 והלאה בכזה פורמט
"{ 01/01/2000 00:00 on }",
"{ 02/01/2000 00:00 off }",
מעתיקים את כל הקוד לתוכנה, משנים מה שרוצים (או משאירים ככה) וצורבים ל ESP.
איך מחברים הכל על לוח ההרחבה?
בלוח ההרחבה יש לכם שלוש שורות של פינים ליד כל מספר. זה מאוד נוח כי לא צריך להלחים כמה חוטים יחד:G: אדמה (GND).
V: מתח (5V).
S: הסיגנל - אלו הפינים שרשומים בקוד.
- חיבור המסך (OLED):
GND: לאחד מהפינים בשורה של ה-G (לא משנה איזה).
VDD / VCC: לאחד מהפינים בשורה של ה-V.
SCK / SCL: לפין 22 בשורה של ה-S.
SDA: לפין 21 בשורה של ה-S.
- חיבור השעון (RTC):
השעון מתחבר בדיוק לאותם מקומות (כי זו תקשורת משותפת):
GND: לאחד מהפינים בשורה של ה-G.
VCC: לאחד מהפינים בשורה של ה-V.
SCL: גם הוא לפין 22 בשורה של ה-S.
SDA: גם הוא לפין 21 בשורה של ה-S. (תעשו את זה עם מפצל או עם לוח חיבורים).
- חיבור הממסר (קונטקטור):
GND: לאחד מהפינים בשורה של ה-G.
VCC: לאחד מהפינים בשורה של ה-V.
IN / Signal: לפין 5 בשורה של ה-S.
- חיבור הכפתורים (4 כפתורים):
לכל כפתור יש 2 רגליים (אם יש 4 אז זה באמת 2 פעמיים). אנחנו מחברים רגל אחת ל-G ורגל אחת ל-S (אין צורך בחיבור ל-V כי הקוד משתמש בנגד פנימי):
כפתור למעלה (UP): רגל אחת ל-G, רגל שנייה לפין 15 ב-S.
כפתור למטה (DOWN): רגל אחת ל-G, רגל שנייה לפין 4 ב-S.
כפתור אישור (SELECT): רגל אחת ל-G, רגל שנייה לפין 16 ב-S.
כפתור חזור (BACK): רגל אחת ל-G, רגל שנייה לפין 17 ב-S.
- חיבור הקונטקטור
DC+ (המתח): מחברים לאחד מהפינים בשורה של ה-5V (יש 2 כאלה) בלוח ההרחבה. זה מספק לו את ה-5V שהוא צריך כדי לעבוד.
DC- (האדמה): מחברים לאחד מהפינים בשורה של ה-G.
IN (הסיגנל): מחברים לפין D5 בשורה של ה-S. זה הפין שנותן את הפקודה "תדליק" או "תכבה".
- חיבור הכוח
בצד השני של המודול יש 3 כניסות. כאן עובר הזרם שמפעיל את הקונטקטור:
החיבור האמצעי (COM): כאן נכנס חוט הפאזה (החום) שמגיע מהחשמל.
החיבור השמאלי או הימני (בד"כ NO - Normally Open): מכאן יוצא החוט שממשיך אל המערכת גז (ניתן לחבר לשקע או כחוט).
את חוט הניוטרל (כחול) ואת חוט ההארקה (צהוב-ירוק) שמגיעים מהקיר, מחברים ישירות למערכת הגז. הם לא עוברים דרך הקונטקטור. (הקונטקטור שהבאתי בקישור מתאים לעד 10 אמפר שזה הרבה מעבר למה שמערכת חימום בגז צריכה)
זהו, עכשיו הכל מחובר ועובד. מי שרוצה ציירתי לוח מודפס מותאם אישית להזמנה, (אמור להתאים אבל עוד לא הגיע אלי אז לא בדקתי אותו)
ניתן להזמין באתר jlcpcb.com להעלות את הקובץ הזה לשם Gerber_PCB.zip ולהזמיןעכשיו להורדת הזמנים
import requests from datetime import datetime, timedelta # --- הגדרות, שנת התחלה ושנת סיום וזמן הדלקת נרות לפני השקיעה--- START_YEAR = 2026 END_YEAR = 2050 CANDLE_LIGHTING_MINUTES = 20 OFFSET_BEFORE_CANDLES = 0 def get_shabbat_times(): all_events = [] for year in range(START_YEAR, END_YEAR + 1): print(f"Fetching data for year: {year}...") url = f"https://www.hebcal.com/hebcal?v=1&cfg=json&maj=on&min=off&mod=off&nx=off&year={year}&month=x&ss=on&mf=off&c=on&geo=city&city=IL-Jerusalem&b={CANDLE_LIGHTING_MINUTES}&m=50" try: response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() for item in data.get('items', []): dt_str = item['date'] if '+' in dt_str: dt_str = dt_str.split('+')[0] dt_obj = datetime.fromisoformat(dt_str) category = item.get('category') if category == "candles": off_time = dt_obj - timedelta(minutes=OFFSET_BEFORE_CANDLES) formatted_date = off_time.strftime("%d/%m/%Y") formatted_time = off_time.strftime("%H:%M") all_events.append(f' "{{ {formatted_date} {formatted_time} off }}",') elif category == "havdalah": formatted_date = dt_obj.strftime("%d/%m/%Y") formatted_time = dt_obj.strftime("%H:%M") all_events.append(f' "{{ {formatted_date} {formatted_time} on }}",') else: print(f"Error {response.status_code}") except Exception as e: print(f"Error: {str(e)}") with open("arduino_ready_schedule.txt", "w") as f: for event in all_events: f.write(event + "\n") print(f"\nDone! Open 'arduino_ready_schedule.txt' and copy everything.") if __name__ == "__main__": get_shabbat_times()להתקין פייתון (תשאלו את ג'מיני או גי פי טי, יסביר לכם אם אתם לא יודעים)
ישמר ליד קובץ טקסט עם זמני כניסת ויציאת שבת וחגים (לפי ירושלים, ניתן לשנות בקישור)
להעתיק כל מה ששם ולהכניס משורה 23 בקוד והלאה.בדקתי בערך 30 זמנים והם היו נכונים.

תמונה של המוצר בנוי (על לוח מחורר, בלי הקונטקטור)
קובץ מוכן ל 5 דקות לפני הדלקת נרות עד 2046 arduino_ready_schedule.txt -
הקדמה קצרה, מי שמכיר (נראה לי שכולם) את המערכת של חימום מים בגז למקלחת, יודע שזה עובד עם אש, וכידוע אש זה דבר שאסור להשתמש בו בשבת, ואצלי בכל אופן (בישיבה) לא תמיד זוכרים לכבות את המערכת, אז יצרתי שעון שבת עם זמני כניסת ויציאת שבת (מצורף סקריפט להורדה של כמה שתרצו) להלן ההסבר המפורט
מה צריך לקנות:- קונים חלק שנקרא ESP32 - מכשיר חכם עם המון אפשרויות, הנה קישור למוצר כזה באליאקספרס קישור affilate ממליץ על הדגם עם הכרטיס הרחבה יותר נוח.
- קונים חלק שנקרא שעון RTC קישור affilate זה אחראי על השעה ותאריך
- מסך קישור affilate לא נראה לי שצריך להסביר מה הוא עושה
- כפתורים קישור affilate כדי ללחוץ :).
- דרך כלשהיא לחבר את כל זה, אפשר עם לוח מחורר קישור affilate או עם לוח בהתאמה אישית (מצורף קובץ לשליחה לאתרים שמייצרים את זה (משלוח יקר) הקובץ מתאים לESP עם הכרטיס הרחבה.) מחברים (לנוחות) קישור affilate
- קונטקטור קישור affilate זה כדי להעביר חשמל 220V
מה עושים עם כל זה? - מתקינים על המחשב תוכנה שנקראת arduino IDE.
- בסרגל העליון, לחצו על File (קובץ) ואז על Preferences (העדפות).
- בחלון שנפתח, חפשו שורה שנקראת: Additional Boards Manager URLs.
- במלבן הריק לידה, תדביקו את הקישור הבא: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json (אם כבר יש שם קישור אחר, פשוט שימו פסיק ביניהם או תרדו שורה). לחצו על OK.
עכשיו כשהתוכנה יודעת איפה לחפש, צריך להוריד את הקבצים בפועל:
-
בצד שמאל של התוכנה, לחצו על האייקון של Boards Manager (זה נראה כמו לוח קטן עם עיגולים).
-
בשורת החיפוש למעלה, הקלידו: ESP32.
תמצאו את החבילה של חברת Espressif Systems ולחצו על כפתור ה-INSTALL. זה יכול לקחת דקה או שתיים, תנו לזה לסיים.
עכשיו כשהכל מותקן, נבחר את הלוח שלנו כדי שהקוד ייצרב נכון:
- לכו ל-Tools (כלים) -> Board -> esp32.
בחרו בדגם: DOIT ESP32 DEVKIT V1.
חברו את ה-ESP32 למחשב עם כבל USB איכותי (שימו לב שיש כבלים שהם רק לטעינה ולא לנתונים, הם לא יעבדו).
שוב ב-Tools, לכו ל-Port ובחרו את היציאה שנוספה (COM3 או משהו דומה).
התקנת הספריות: - כדי שהקוד שכתבנו יבין מה זה מסך ומה זה שעון, צריך להוריד לו ספריות מוכנות. לחצו על האייקון של ה-Library Manager (המדף מתחת ללוחות) וחפשו והתקינו את אלו:
Adafruit SSD1306
Adafruit GFX Library
RTClib (של Adafruit)
עכשיו לתכנות בפועל:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <RTClib.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); RTC_DS3231 rtc; // כאן מוגדרים הפינים של כל דבר, ניתן לשנות אותם (יש מספרים על הלוח עצמו) const int RELAY_PIN = 5; // זה הממסר (קונטקטור) const int BTN_UP = 15, BTN_DOWN = 4, BTN_SELECT = 16, BTN_BACK = 17; // זה הכפתורים bool isSetupMode = false; int setupStep = 0; int editYear, editMonth, editDay, editHour, editMin; String nextEventStr = "Scanning..."; bool manualOverride = false; bool lastDSTState = false; // כאן כותבים את כל האירועים (כיבוי והדלקה, מצורף סקריפט פייתון להורדת רשימה ארוכה של זמנים) const char* const schedule[] PROGMEM = { "{ 01/01/2000 00:00 on }", }; const int scheduleSize = sizeof(schedule) / sizeof(schedule[0]); // --- אייקונים --- (לא יצא יפה, זה באשמת ג'מיני) const unsigned char hand_icon[] PROGMEM = { 0x18, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x00, 0x3C, 0x00 }; const unsigned char sun_icon[] PROGMEM = { 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0xfe, 0x00, 0x38, 0x00, 0x54, 0x00, 0x10, 0x00 }; const unsigned char rain_icon[] PROGMEM = { 0x10, 0x00, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00, 0x10, 0x00, 0x54, 0x00, 0x38, 0x00 }; bool isIsraelDST(DateTime t) { int y = t.year(); int m = t.month(); int d = t.day(); if (m < 3 || m > 10) return false; if (m > 3 && m < 10) return true; if (m == 3) { int lastFriday = 31 - ((DateTime(y, 3, 31).dayOfTheWeek() + 2) % 7); return (d > lastFriday) || (d == lastFriday && t.hour() >= 2); } if (m == 10) { int lastSunday = 31 - DateTime(y, 10, 31).dayOfTheWeek(); return (d < lastSunday) || (d == lastSunday && t.hour() < 2); } return false; } void updateDST() { DateTime now = rtc.now(); bool currentDST = isIsraelDST(now); if (currentDST != lastDSTState) { if (now.hour() == 2 && now.minute() == 0) { if (currentDST) { rtc.adjust(now + TimeSpan(0, 1, 0, 0)); } else { rtc.adjust(now - TimeSpan(0, 1, 0, 0)); } lastDSTState = currentDST; syncWithSchedule(rtc.now()); display.clearDisplay(); DateTime updated = rtc.now(); display.setTextSize(3); display.setCursor(20, 0); display.printf("%02d:%02d", updated.hour(), updated.minute()); display.setTextSize(1); display.setCursor(35, 30); display.printf("%02d/%02d/%04d", updated.day(), updated.month(), updated.year()); display.display(); Serial.println("DST Adjustment applied!"); } else { lastDSTState = currentDST; } } } void setup() { Serial.begin(115200); pinMode(RELAY_PIN, OUTPUT); pinMode(BTN_UP, INPUT_PULLUP); pinMode(BTN_DOWN, INPUT_PULLUP); pinMode(BTN_SELECT, INPUT_PULLUP); pinMode(BTN_BACK, INPUT_PULLUP); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) Serial.println("OLED Fail"); rtc.begin(); lastDSTState = isIsraelDST(rtc.now()); display.clearDisplay(); display.setTextColor(WHITE); syncWithSchedule(rtc.now()); } void syncWithSchedule(DateTime now) { int lastPassedEvent = -1; bool foundNext = false; for (int i = 0; i < scheduleSize; i++) { char buffer[40]; strcpy_P(buffer, (char*)pgm_read_ptr(&(schedule[i]))); String line = String(buffer); int d = line.substring(2, 4).toInt(); int m = line.substring(5, 7).toInt(); int y = line.substring(8, 12).toInt(); int hr = line.substring(13, 15).toInt(); int mn = line.substring(16, 18).toInt(); DateTime eventTime(y, m, d, hr, mn, 0); if (eventTime.unixtime() <= now.unixtime()) { lastPassedEvent = i; } else if (!foundNext) { nextEventStr = String(d) + "/" + String(m) + " " + String(hr) + ":" + (mn < 10 ? "0" : "") + String(mn); nextEventStr += (line.indexOf("on") != -1) ? " ON" : " OFF"; foundNext = true; } } if (lastPassedEvent != -1 && !manualOverride) { char buffer[40]; strcpy_P(buffer, (char*)pgm_read_ptr(&(schedule[lastPassedEvent]))); String lastLine = String(buffer); digitalWrite(RELAY_PIN, (lastLine.indexOf("on") != -1) ? HIGH : LOW); } if (!foundNext) nextEventStr = "END OF LIST"; static int lastKnownEvent = -1; if (lastPassedEvent != lastKnownEvent) { manualOverride = false; lastKnownEvent = lastPassedEvent; } } void loop() { updateDST(); DateTime now = rtc.now(); if (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW && !isSetupMode) { unsigned long startManual = millis(); while(digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW) { if (millis() - startManual > 1000) break; } if (millis() - startManual > 1000) { if (!manualOverride) { manualOverride = true; digitalWrite(RELAY_PIN, HIGH); } else if (digitalRead(RELAY_PIN) == HIGH) { digitalWrite(RELAY_PIN, LOW); } else { manualOverride = false; syncWithSchedule(now); } display.clearDisplay(); display.setCursor(10, 25); display.setTextSize(2); if (!manualOverride) display.print("AUTO MODE"); else display.print(digitalRead(RELAY_PIN) ? "MANUAL ON" : "MANUAL OFF"); display.display(); while(digitalRead(BTN_UP) == LOW || digitalRead(BTN_DOWN) == LOW); delay(300); } } if (digitalRead(BTN_SELECT) == LOW && !isSetupMode) { unsigned long start = millis(); while(digitalRead(BTN_SELECT) == LOW) if (millis() - start > 2000) break; if (millis() - start > 2000) { isSetupMode = true; editYear = now.year(); editMonth = now.month(); editDay = now.day(); editHour = now.hour(); editMin = now.minute(); setupStep = 0; while(digitalRead(BTN_SELECT) == LOW); delay(200); } } display.clearDisplay(); if (isSetupMode) { runSetupMenu(); } else { if (now.second() == 0 || now.second() % 30 == 0) { syncWithSchedule(now); } display.setTextSize(3); display.setCursor(20, 0); display.printf("%02d:%02d", now.hour(), now.minute()); display.setTextSize(1); display.setCursor(35, 30); display.printf("%02d/%02d/%04d", now.day(), now.month(), now.year()); if (isIsraelDST(now)) { display.drawBitmap(110, 28, sun_icon, 12, 12, WHITE); } else { display.drawBitmap(110, 28, rain_icon, 12, 12, WHITE); } display.drawLine(0, 42, 128, 42, WHITE); display.setCursor(0, 48); display.print("next: "); display.print(nextEventStr); display.setCursor(0, 57); display.print("now: "); display.print(digitalRead(RELAY_PIN) ? "ON" : "OFF"); if (manualOverride) display.drawBitmap(110, 52, hand_icon, 12, 12, WHITE); } display.display(); delay(50); } void runSetupMenu() { display.setTextSize(1); display.setCursor(0,0); display.println("SETUP TIME"); display.setTextSize(2); display.setCursor(0, 20); if(setupStep==0) display.printf("Year: %d", editYear); else if(setupStep==1) display.printf("Month: %d", editMonth); else if(setupStep==2) display.printf("Day: %d", editDay); else if(setupStep==3) display.printf("Hour: %d", editHour); else if(setupStep==4) display.printf("Min: %d", editMin); if (digitalRead(BTN_UP) == LOW) { if(setupStep==0) editYear++; else if(setupStep==1) editMonth=(editMonth % 12) + 1; else if(setupStep==2) editDay=(editDay % 31) + 1; else if(setupStep==3) editHour=(editHour + 1) % 24; else if(setupStep==4) editMin=(editMin + 1) % 60; delay(200); } if (digitalRead(BTN_DOWN) == LOW) { if (setupStep == 0) editYear--; else if (setupStep == 1) { editMonth--; if (editMonth < 1) editMonth = 12; } else if (setupStep == 2) { editDay--; if (editDay < 1) editDay = 31; } else if (setupStep == 3) { editHour--; if (editHour < 0) editHour = 23; } else if (setupStep == 4) { editMin--; if (editMin < 0) editMin = 59; } delay(200); } if (digitalRead(BTN_SELECT) == LOW) { setupStep++; while(digitalRead(BTN_SELECT) == LOW); delay(200); if (setupStep > 4) { rtc.adjust(DateTime(editYear, editMonth, editDay, editHour, editMin, 0)); lastDSTState = isIsraelDST(rtc.now()); isSetupMode = false; manualOverride = false; syncWithSchedule(rtc.now()); } } if (digitalRead(BTN_BACK) == LOW) { isSetupMode = false; while(digitalRead(BTN_BACK) == LOW); delay(200); } }הסבר על הקוד
- המסך מוגדר ברזולוציית 128X64 זה המסך ששמתי בקישור, אם יש לכם מסך אחר תשנו את זה לרזולוציה שלו
- הפינים: RELAY_PIN = 5; זה אומר שהפין שממנו יוצא חשמל כשדלוק זה פין D5. וכן הכפתורים, 15, 4, 16, 17 (הם אחד ליד השני, המספרים סתם קופצים).
- בקוד הזה יש 4 כפתורים, למעלה, למטה, אישור וחזור. לחיצה ארוכה על כפתור אישור מכניסה את המערכת למצב כיוון שעה ותאריך.
לחיצה ארוכה על למעלה ולמטה הופכת את המערכת למצב ידני (דלוק, כבוי) מתאפס כשמשתנה תוכנית. - שעון חורף/קיץ מתחלף אוטומטית
- את הזמנים שמים בשורה 23 והלאה בכזה פורמט
"{ 01/01/2000 00:00 on }",
"{ 02/01/2000 00:00 off }",
מעתיקים את כל הקוד לתוכנה, משנים מה שרוצים (או משאירים ככה) וצורבים ל ESP.
איך מחברים הכל על לוח ההרחבה?
בלוח ההרחבה יש לכם שלוש שורות של פינים ליד כל מספר. זה מאוד נוח כי לא צריך להלחים כמה חוטים יחד:G: אדמה (GND).
V: מתח (5V).
S: הסיגנל - אלו הפינים שרשומים בקוד.
- חיבור המסך (OLED):
GND: לאחד מהפינים בשורה של ה-G (לא משנה איזה).
VDD / VCC: לאחד מהפינים בשורה של ה-V.
SCK / SCL: לפין 22 בשורה של ה-S.
SDA: לפין 21 בשורה של ה-S.
- חיבור השעון (RTC):
השעון מתחבר בדיוק לאותם מקומות (כי זו תקשורת משותפת):
GND: לאחד מהפינים בשורה של ה-G.
VCC: לאחד מהפינים בשורה של ה-V.
SCL: גם הוא לפין 22 בשורה של ה-S.
SDA: גם הוא לפין 21 בשורה של ה-S. (תעשו את זה עם מפצל או עם לוח חיבורים).
- חיבור הממסר (קונטקטור):
GND: לאחד מהפינים בשורה של ה-G.
VCC: לאחד מהפינים בשורה של ה-V.
IN / Signal: לפין 5 בשורה של ה-S.
- חיבור הכפתורים (4 כפתורים):
לכל כפתור יש 2 רגליים (אם יש 4 אז זה באמת 2 פעמיים). אנחנו מחברים רגל אחת ל-G ורגל אחת ל-S (אין צורך בחיבור ל-V כי הקוד משתמש בנגד פנימי):
כפתור למעלה (UP): רגל אחת ל-G, רגל שנייה לפין 15 ב-S.
כפתור למטה (DOWN): רגל אחת ל-G, רגל שנייה לפין 4 ב-S.
כפתור אישור (SELECT): רגל אחת ל-G, רגל שנייה לפין 16 ב-S.
כפתור חזור (BACK): רגל אחת ל-G, רגל שנייה לפין 17 ב-S.
- חיבור הקונטקטור
DC+ (המתח): מחברים לאחד מהפינים בשורה של ה-5V (יש 2 כאלה) בלוח ההרחבה. זה מספק לו את ה-5V שהוא צריך כדי לעבוד.
DC- (האדמה): מחברים לאחד מהפינים בשורה של ה-G.
IN (הסיגנל): מחברים לפין D5 בשורה של ה-S. זה הפין שנותן את הפקודה "תדליק" או "תכבה".
- חיבור הכוח
בצד השני של המודול יש 3 כניסות. כאן עובר הזרם שמפעיל את הקונטקטור:
החיבור האמצעי (COM): כאן נכנס חוט הפאזה (החום) שמגיע מהחשמל.
החיבור השמאלי או הימני (בד"כ NO - Normally Open): מכאן יוצא החוט שממשיך אל המערכת גז (ניתן לחבר לשקע או כחוט).
את חוט הניוטרל (כחול) ואת חוט ההארקה (צהוב-ירוק) שמגיעים מהקיר, מחברים ישירות למערכת הגז. הם לא עוברים דרך הקונטקטור. (הקונטקטור שהבאתי בקישור מתאים לעד 10 אמפר שזה הרבה מעבר למה שמערכת חימום בגז צריכה)
זהו, עכשיו הכל מחובר ועובד. מי שרוצה ציירתי לוח מודפס מותאם אישית להזמנה, (אמור להתאים אבל עוד לא הגיע אלי אז לא בדקתי אותו)
ניתן להזמין באתר jlcpcb.com להעלות את הקובץ הזה לשם Gerber_PCB.zip ולהזמיןעכשיו להורדת הזמנים
import requests from datetime import datetime, timedelta # --- הגדרות, שנת התחלה ושנת סיום וזמן הדלקת נרות לפני השקיעה--- START_YEAR = 2026 END_YEAR = 2050 CANDLE_LIGHTING_MINUTES = 20 OFFSET_BEFORE_CANDLES = 0 def get_shabbat_times(): all_events = [] for year in range(START_YEAR, END_YEAR + 1): print(f"Fetching data for year: {year}...") url = f"https://www.hebcal.com/hebcal?v=1&cfg=json&maj=on&min=off&mod=off&nx=off&year={year}&month=x&ss=on&mf=off&c=on&geo=city&city=IL-Jerusalem&b={CANDLE_LIGHTING_MINUTES}&m=50" try: response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() for item in data.get('items', []): dt_str = item['date'] if '+' in dt_str: dt_str = dt_str.split('+')[0] dt_obj = datetime.fromisoformat(dt_str) category = item.get('category') if category == "candles": off_time = dt_obj - timedelta(minutes=OFFSET_BEFORE_CANDLES) formatted_date = off_time.strftime("%d/%m/%Y") formatted_time = off_time.strftime("%H:%M") all_events.append(f' "{{ {formatted_date} {formatted_time} off }}",') elif category == "havdalah": formatted_date = dt_obj.strftime("%d/%m/%Y") formatted_time = dt_obj.strftime("%H:%M") all_events.append(f' "{{ {formatted_date} {formatted_time} on }}",') else: print(f"Error {response.status_code}") except Exception as e: print(f"Error: {str(e)}") with open("arduino_ready_schedule.txt", "w") as f: for event in all_events: f.write(event + "\n") print(f"\nDone! Open 'arduino_ready_schedule.txt' and copy everything.") if __name__ == "__main__": get_shabbat_times()להתקין פייתון (תשאלו את ג'מיני או גי פי טי, יסביר לכם אם אתם לא יודעים)
ישמר ליד קובץ טקסט עם זמני כניסת ויציאת שבת וחגים (לפי ירושלים, ניתן לשנות בקישור)
להעתיק כל מה ששם ולהכניס משורה 23 בקוד והלאה.בדקתי בערך 30 זמנים והם היו נכונים.

תמונה של המוצר בנוי (על לוח מחורר, בלי הקונטקטור)
קובץ מוכן ל 5 דקות לפני הדלקת נרות עד 2046 arduino_ready_schedule.txt -
תודה רבה!!
אני כנראה ישתמש בלפחות חלק מהרעיונות שהסברת פה איך יישמת.יש גם פיתרון פשוט יותר לאנשים שאוהבים דברים מוכנים,
התקן שבתומט (עולה כ600-800 שקל)
ולחבר אותו למגען שינתק את החשמל.
[https://bar-ore.com/שבתומט/](כתובת קישור) -
תודה רבה!!
אני כנראה ישתמש בלפחות חלק מהרעיונות שהסברת פה איך יישמת.יש גם פיתרון פשוט יותר לאנשים שאוהבים דברים מוכנים,
התקן שבתומט (עולה כ600-800 שקל)
ולחבר אותו למגען שינתק את החשמל.
[https://bar-ore.com/שבתומט/](כתובת קישור)