|
5 | 5 | from datetime import datetime, timezone |
6 | 6 |
|
7 | 7 | try: |
8 | | - from sense_hat import SenseHat |
| 8 | + from sense_hat import SenseHat |
9 | 9 | except Exception as e: |
10 | | - SenseHat = None |
| 10 | + SenseHat = None |
11 | 11 |
|
12 | 12 |
|
13 | 13 | def iso_timestamp(): |
14 | | - return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") |
| 14 | + return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") |
| 15 | + |
| 16 | + |
| 17 | +def safe_float(value): |
| 18 | + try: |
| 19 | + return float(value) if value is not None else None |
| 20 | + except Exception: |
| 21 | + return None |
15 | 22 |
|
16 | 23 |
|
17 | 24 | def read_sensors(): |
18 | | - if SenseHat is None: |
19 | | - return {"error": "sense_hat library not available"} |
20 | | - try: |
21 | | - sh = SenseHat() |
22 | | - temperature = sh.get_temperature() |
23 | | - humidity = sh.get_humidity() |
24 | | - pressure = sh.get_pressure() |
25 | | - |
26 | | - # Orientation |
27 | | - orientation = sh.get_orientation() or {} |
28 | | - pitch = orientation.get("pitch") |
29 | | - roll = orientation.get("roll") |
30 | | - yaw = orientation.get("yaw") |
31 | | - |
32 | | - # IMU sensors |
33 | | - accel_raw = sh.get_accelerometer_raw() or {} |
34 | | - gyro_raw = sh.get_gyroscope_raw() or {} |
35 | | - mag_raw = sh.get_compass_raw() or {} |
36 | | - |
37 | | - return { |
38 | | - "temperature": float(temperature) if temperature is not None else None, |
39 | | - "humidity": float(humidity) if humidity is not None else None, |
40 | | - "pressure": float(pressure) if pressure is not None else None, |
41 | | - "orientation": { |
42 | | - "pitch": float(pitch) if pitch is not None else None, |
43 | | - "roll": float(roll) if roll is not None else None, |
44 | | - "yaw": float(yaw) if yaw is not None else None, |
45 | | - }, |
46 | | - "accelerometer": { |
47 | | - "x": float(accel_raw.get("x")) if accel_raw.get("x") is not None else None, |
48 | | - "y": float(accel_raw.get("y")) if accel_raw.get("y") is not None else None, |
49 | | - "z": float(accel_raw.get("z")) if accel_raw.get("z") is not None else None, |
50 | | - }, |
51 | | - "gyroscope": { |
52 | | - "x": float(gyro_raw.get("x")) if gyro_raw.get("x") is not None else None, |
53 | | - "y": float(gyro_raw.get("y")) if gyro_raw.get("y") is not None else None, |
54 | | - "z": float(gyro_raw.get("z")) if gyro_raw.get("z") is not None else None, |
55 | | - }, |
56 | | - "magnetometer": { |
57 | | - "x": float(mag_raw.get("x")) if mag_raw.get("x") is not None else None, |
58 | | - "y": float(mag_raw.get("y")) if mag_raw.get("y") is not None else None, |
59 | | - "z": float(mag_raw.get("z")) if mag_raw.get("z") is not None else None, |
60 | | - }, |
61 | | - "timestamp": iso_timestamp(), |
62 | | - } |
63 | | - except Exception as e: |
64 | | - return {"error": f"Exception during sensor read: {e}"} |
| 25 | + if SenseHat is None: |
| 26 | + return {"error": "sense_hat library not available"} |
| 27 | + |
| 28 | + try: |
| 29 | + sh = SenseHat() |
| 30 | + except Exception as e: |
| 31 | + # If we can't even init the SenseHat, we really are stuck |
| 32 | + return {"error": f"SenseHat init failed: {e}"} |
| 33 | + |
| 34 | + # Read each sensor separately so one failure doesn't kill everything |
| 35 | + try: |
| 36 | + temperature = safe_float(sh.get_temperature()) |
| 37 | + except Exception: |
| 38 | + temperature = None |
| 39 | + |
| 40 | + try: |
| 41 | + humidity = safe_float(sh.get_humidity()) |
| 42 | + except Exception: |
| 43 | + humidity = None |
| 44 | + |
| 45 | + try: |
| 46 | + pressure = safe_float(sh.get_pressure()) |
| 47 | + except Exception: |
| 48 | + pressure = None |
| 49 | + |
| 50 | + # Orientation |
| 51 | + try: |
| 52 | + orientation = sh.get_orientation() or {} |
| 53 | + except Exception: |
| 54 | + orientation = {} |
| 55 | + |
| 56 | + pitch = orientation.get("pitch") |
| 57 | + roll = orientation.get("roll") |
| 58 | + yaw = orientation.get("yaw") |
| 59 | + |
| 60 | + # IMU sensors |
| 61 | + try: |
| 62 | + accel_raw = sh.get_accelerometer_raw() or {} |
| 63 | + except Exception: |
| 64 | + accel_raw = {} |
| 65 | + |
| 66 | + try: |
| 67 | + gyro_raw = sh.get_gyroscope_raw() or {} |
| 68 | + except Exception: |
| 69 | + gyro_raw = {} |
| 70 | + |
| 71 | + try: |
| 72 | + mag_raw = sh.get_compass_raw() or {} |
| 73 | + except Exception: |
| 74 | + mag_raw = {} |
| 75 | + |
| 76 | + return { |
| 77 | + "temperature": temperature, |
| 78 | + "humidity": humidity, |
| 79 | + "pressure": pressure, |
| 80 | + "orientation": { |
| 81 | + "pitch": safe_float(pitch), |
| 82 | + "roll": safe_float(roll), |
| 83 | + "yaw": safe_float(yaw) |
| 84 | + }, |
| 85 | + "accelerometer": { |
| 86 | + "x": safe_float(accel_raw.get("x")), |
| 87 | + "y": safe_float(accel_raw.get("y")), |
| 88 | + "z": safe_float(accel_raw.get("z")) |
| 89 | + }, |
| 90 | + "gyroscope": { |
| 91 | + "x": safe_float(gyro_raw.get("x")), |
| 92 | + "y": safe_float(gyro_raw.get("y")), |
| 93 | + "z": safe_float(gyro_raw.get("z")) |
| 94 | + }, |
| 95 | + "magnetometer": { |
| 96 | + "x": safe_float(mag_raw.get("x")), |
| 97 | + "y": safe_float(mag_raw.get("y")), |
| 98 | + "z": safe_float(mag_raw.get("z")) |
| 99 | + }, |
| 100 | + "timestamp": iso_timestamp(), |
| 101 | + } |
65 | 102 |
|
66 | 103 |
|
67 | 104 | def parse_color(s): |
68 | | - parts = [p.strip() for p in s.split(",")] |
69 | | - if len(parts) != 3: |
70 | | - raise ValueError("Color must be r,g,b") |
71 | | - rgb = [] |
72 | | - for p in parts: |
73 | | - v = int(p) |
74 | | - if v < 0: |
75 | | - v = 0 |
76 | | - if v > 255: |
77 | | - v = 255 |
78 | | - rgb.append(v) |
79 | | - return tuple(rgb) |
| 105 | + parts = [p.strip() for p in s.split(",")] |
| 106 | + if len(parts) != 3: |
| 107 | + raise ValueError("Color must be r,g,b") |
| 108 | + rgb = [] |
| 109 | + for p in parts: |
| 110 | + v = int(p) |
| 111 | + if v < 0: |
| 112 | + v = 0 |
| 113 | + if v > 255: |
| 114 | + v = 255 |
| 115 | + rgb.append(v) |
| 116 | + return tuple(rgb) |
80 | 117 |
|
81 | 118 |
|
82 | 119 | def apply_led(args): |
83 | | - if SenseHat is None: |
84 | | - print("sense_hat library not available", file=sys.stderr) |
85 | | - return 1 |
86 | | - try: |
87 | | - sh = SenseHat() |
88 | | - if args.clear: |
89 | | - sh.clear() |
90 | | - return 0 |
91 | | - if args.mode == "text": |
92 | | - color = parse_color(args.color) if args.color else (255, 255, 255) |
93 | | - sh.clear() |
94 | | - sh.show_message(args.text or "", text_colour=color, scroll_speed=0.07) |
95 | | - return 0 |
96 | | - # default status mode |
97 | | - color = parse_color(args.color) if args.color else (0, 255, 0) |
98 | | - sh.clear(color) |
99 | | - return 0 |
100 | | - except Exception as e: |
101 | | - print(f"LED command failed: {e}", file=sys.stderr) |
102 | | - return 2 |
| 120 | + if SenseHat is None: |
| 121 | + print("sense_hat library not available", file=sys.stderr) |
| 122 | + return 1 |
| 123 | + try: |
| 124 | + sh = SenseHat() |
| 125 | + if args.clear: |
| 126 | + sh.clear() |
| 127 | + return 0 |
| 128 | + if args.mode == "text": |
| 129 | + color = parse_color(args.color) if args.color else (255, 255, 255) |
| 130 | + sh.clear() |
| 131 | + sh.show_message(args.text or "", text_colour=color, scroll_speed=0.07) |
| 132 | + return 0 |
| 133 | + # default status mode |
| 134 | + color = parse_color(args.color) if args.color else (0, 255, 0) |
| 135 | + sh.clear(color) |
| 136 | + return 0 |
| 137 | + except Exception as e: |
| 138 | + print(f"LED command failed: {e}", file=sys.stderr) |
| 139 | + return 2 |
103 | 140 |
|
104 | 141 |
|
105 | 142 | def main(): |
106 | | - parser = argparse.ArgumentParser(description="Sense HAT reader and LED controller") |
107 | | - parser.add_argument("--read", action="store_true", help="Read sensors and output JSON") |
108 | | - parser.add_argument("--mode", choices=["status", "text"], help="LED mode") |
109 | | - parser.add_argument("--text", help="Text to display when mode=text") |
110 | | - parser.add_argument("--color", help="Color as r,g,b") |
111 | | - parser.add_argument("--clear", action="store_true", help="Clear LED matrix") |
| 143 | + parser = argparse.ArgumentParser(description="Sense HAT reader and LED controller") |
| 144 | + parser.add_argument("--read", action="store_true", help="Read sensors and output JSON") |
| 145 | + parser.add_argument("--mode", choices=["status", "text"], help="LED mode") |
| 146 | + parser.add_argument("--text", help="Text to display when mode=text") |
| 147 | + parser.add_argument("--color", help="Color as r,g,b") |
| 148 | + parser.add_argument("--clear", action="store_true", help="Clear LED matrix") |
112 | 149 |
|
113 | | - args = parser.parse_args() |
| 150 | + args = parser.parse_args() |
114 | 151 |
|
115 | | - if args.read or (not args.mode and not args.clear): |
116 | | - data = read_sensors() |
117 | | - print(json.dumps(data)) |
118 | | - return 0 if "error" not in data else 3 |
| 152 | + if args.read or (not args.mode and not args.clear): |
| 153 | + data = read_sensors() |
| 154 | + print(json.dumps(data)) |
| 155 | + return 0 if "error" not in data else 3 |
119 | 156 |
|
120 | | - # Otherwise LED control |
121 | | - return apply_led(args) |
| 157 | + # Otherwise LED control |
| 158 | + return apply_led(args) |
122 | 159 |
|
123 | 160 |
|
124 | 161 | if __name__ == "__main__": |
125 | | - sys.exit(main()) |
| 162 | + sys.exit(main()) |
0 commit comments