树莓派3B中Systemd服务运行Python脚本无法连接Firebase实时数据库
Hey there! Let's work through your problem and answer each of your questions clearly, plus give you actionable fixes and optimizations.
1. Why isn't the script connecting to Firebase when running as a Systemd service?
The most likely culprits are environment differences between your interactive terminal session and the Systemd service context, or incomplete network readiness even after network-online.target:
- Minimal environment variables: Your
piuser's terminal has a full set of environment variables (likePATH,HOME, or certificate store paths), but Systemd services run with a stripped-down environment by default. Pyrebase might rely on these variables for things like DNS resolution or SSL certificate validation. - DNS not fully ready:
network-online.targetensures the network is up, but it doesn't always guarantee DNS resolution is fully initialized. Yourhave_internet()checks connectivity to Google, but Firebase requires resolving its specific domain, which might not be ready when the service starts. - Silent stream thread failures: The Firebase stream runs in a background thread. If this thread hits an unhandled exception (like a connection timeout), it might die silently without triggering updates—something you'd see in terminal logs but not in Systemd unless you're logging properly.
2. Do I need service-specific code adjustments?
Absolutely! Here are key tweaks to make your script play nice with Systemd:
- Handle shutdown signals: Systemd sends
SIGTERMwhen stopping the service—your current script doesn't clean up the Firebase stream or GPIO, which can leave resources hanging. - Set explicit working directory: Systemd runs services from
/by default. Adding a working directory ensures your script uses its own folder (critical if you ever add relative paths for configs or logs). - Log to Systemd journal: Replace
print()statements with Python'sloggingmodule so you can see output in Systemd logs.
3. Python code optimization suggestions (for beginners)
Let's clean up your code to make it more robust and maintainable:
- Avoid global variables: The
LED1global is accessed from two threads (stream handler and main loop), which can cause race conditions. Use athreading.Lockto safely update/read the value. - Use GPIO context managers: This ensures GPIO pins are cleaned up automatically even if the script crashes.
- Externalize Firebase config: Don't hardcode API keys in your script—put them in a separate
config.jsonfile. This is safer and easier to update without editing code. - Add stream reconnection logic: If the Firebase stream drops (e.g., network blip), your script should automatically reconnect instead of stopping.
- Test Firebase directly: Your
have_internet()checks Google, but testing connectivity to your Firebase database URL is more reliable.
Here's a revised version of your code with these changes:
#!/usr/bin/env python3 import RPi.GPIO as GPIO import pyrebase import time import httplib2 import json import logging import threading from signal import signal, SIGTERM, SIGINT # Set up logging to write to Systemd journal logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Thread-safe LED status with lock led1_status = "OFF" status_lock = threading.Lock() def updateLED(status_LED, adr_LED): with status_lock: current_state = GPIO.input(adr_LED) if status_LED == "ON" and current_state == GPIO.LOW: GPIO.output(adr_LED, GPIO.HIGH) logger.info("LED turned ON") elif status_LED == "OFF" and current_state == GPIO.HIGH: GPIO.output(adr_LED, GPIO.LOW) logger.info("LED turned OFF") def have_firebase_connection(database_url): # Check connectivity directly to your Firebase database db_domain = database_url.split("//")[-1] conn = httplib2.HTTPConnectionWithTimeout(db_domain, timeout=5) try: conn.request("HEAD", "/") response = conn.getresponse() conn.close() return response.status == 200 except Exception as e: logger.error(f"Firebase connection check failed: {str(e)}") conn.close() return False def stream_handler(message): global led1_status new_status = message["data"] with status_lock: led1_status = new_status logger.info(f"Received new status from Firebase: {new_status}") updateLED(new_status, LED1_adr) def cleanup(signum, frame): logger.info("Shutting down service...") if 'my_stream' in globals(): my_stream.close() GPIO.cleanup() exit(0) # Register signal handlers for Systemd stop commands signal(SIGTERM, cleanup) signal(SIGINT, cleanup) # GPIO setup LED1_adr = 18 GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(LED1_adr, GPIO.OUT) GPIO.output(LED1_adr, GPIO.HIGH) # Wait for Firebase connectivity logger.info("Waiting for Firebase connection...") # Load database URL from config first (create config.json with your Firebase settings) with open("/home/pi/Desktop/FirebaseTest/config.json", "r") as f: config = json.load(f) database_url = config["databaseURL"] while not have_firebase_connection(database_url): GPIO.output(LED1_adr, GPIO.LOW) logger.warning("No Firebase connection, retrying...") time.sleep(1) GPIO.output(LED1_adr, GPIO.HIGH) # Initialize Firebase firebase = pyrebase.initialize_app(config) database = firebase.database() logger.info("Connected to Firebase successfully") # Start stream with reconnection logic my_stream = None while True: try: if my_stream is None: my_stream = database.child("/led1/status").stream(stream_handler) logger.info("Firebase stream started") time.sleep(0.25) # Update LED in main loop (redundant but safe) with status_lock: updateLED(led1_status, LED1_adr) except Exception as e: logger.error(f"Stream error occurred: {str(e)}, reconnecting...") if my_stream: my_stream.close() my_stream = None time.sleep(2)
4. Debugging steps to diagnose the issue
Here's how to get to the bottom of why the service isn't working:
- Check Systemd logs: Run
journalctl -u MyScriptService -f(replace with your service name) to see real-time logs. Look for errors like Firebase connection failures or stream initialization issues. - Compare environments: Temporarily modify your Systemd
ExecStartto run/usr/bin/envinstead of the script. Check the logs withjournalctlto see what environment variables are available—compare this to your terminal'senvoutput to spot missing variables. - Add verbose logging: Update your script to log every critical step (Firebase initialization, stream start, status updates) so you can see where it's getting stuck.
- Revise Systemd service config: Use the improved config below to ensure network readiness and proper logging.
Revised Systemd Service Config
Here's an optimized version of your service file:
[Unit] Description=Firebase LED Controller Service Wants=network-online.target nss-lookup.target After=network.target network-online.target nss-lookup.target # Wait for DNS resolution [Service] Type=simple User=pi WorkingDirectory=/home/pi/Desktop/FirebaseTest ExecStart=/usr/bin/python3 /home/pi/Desktop/FirebaseTest/firebaseTest.py StandardOutput=journal+console StandardError=journal+console Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target
After modifying the service file, reload Systemd and restart the service:
sudo systemctl daemon-reload sudo systemctl restart MyScriptService
内容的提问来源于stack exchange,提问作者Jakub Szlaur

