scale_diag.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. """NAU7802 Scale Diagnostic.
  3. I2C address: 0x2A
  4. Bus: /dev/i2c-1 (GPIO2/GPIO3 on RPi)
  5. """
  6. import os
  7. import sys
  8. import time
  9. import smbus2
  10. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "daemon")))
  11. from nau7802 import NAU7802
  12. def _env_int(name: str, default: int) -> int:
  13. value = os.environ.get(name)
  14. if value is None or value == "":
  15. return default
  16. try:
  17. return int(value)
  18. except ValueError:
  19. return default
  20. I2C_BUS = _env_int("SPOOLBUDDY_I2C_BUS", 1)
  21. NAU7802_ADDR = 0x2A
  22. # Register addresses
  23. REG_PU_CTRL = 0x00
  24. REG_CTRL1 = 0x01
  25. REG_CTRL2 = 0x02
  26. REG_ADCO_B2 = 0x12 # ADC output MSB
  27. REG_ADCO_B1 = 0x13
  28. REG_ADCO_B0 = 0x14 # ADC output LSB
  29. REG_ADC = 0x15
  30. REG_PGA = 0x1B
  31. REG_PWR_CTRL = 0x1C
  32. REG_REVISION = 0x1F
  33. # PU_CTRL bits
  34. PU_RR = 0x01 # Register reset
  35. PU_PUD = 0x02 # Power up digital
  36. PU_PUA = 0x04 # Power up analog
  37. PU_PUR = 0x08 # Power up ready (read-only)
  38. PU_CS = 0x10 # Cycle start
  39. PU_CR = 0x20 # Cycle ready (read-only)
  40. PU_OSCS = 0x40 # Oscillator select
  41. PU_AVDDS = 0x80 # AVDD source select
  42. def main():
  43. print("=" * 60)
  44. print("NAU7802 Scale Diagnostic")
  45. print("=" * 60)
  46. print(f"Configured bus: {I2C_BUS}, address: 0x{NAU7802_ADDR:02X}")
  47. # Probe both common I2C buses and show where devices are actually visible.
  48. found_by_bus: dict[int, list[int]] = {}
  49. for bus_num in (0, 1):
  50. found_by_bus[bus_num] = []
  51. try:
  52. with smbus2.SMBus(bus_num) as probe_bus:
  53. for addr in range(0x03, 0x78):
  54. try:
  55. probe_bus.read_byte(addr)
  56. found_by_bus[bus_num].append(addr)
  57. except OSError:
  58. continue
  59. except FileNotFoundError:
  60. continue
  61. except PermissionError:
  62. continue
  63. for bus_num, addrs in found_by_bus.items():
  64. if addrs:
  65. pretty = " ".join(f"0x{a:02X}" for a in addrs)
  66. print(f"Bus {bus_num} devices: {pretty}")
  67. else:
  68. print(f"Bus {bus_num} devices: (none)")
  69. if NAU7802_ADDR not in found_by_bus.get(I2C_BUS, []):
  70. for alt in (1, 0):
  71. if alt != I2C_BUS and NAU7802_ADDR in found_by_bus.get(alt, []):
  72. print(f"\nHint: NAU7802 (0x{NAU7802_ADDR:02X}) appears on bus {alt}, not configured bus {I2C_BUS}.")
  73. print(f"Try: SPOOLBUDDY_I2C_BUS={alt} .../scale_diag.py")
  74. break
  75. scale = NAU7802()
  76. try:
  77. print("[1] Initializing...")
  78. scale.init()
  79. # Print key interpreted config values
  80. revision = scale.read_reg(REG_REVISION)
  81. revision_id = revision & 0x0F
  82. print(f" Revision ID: {revision_id}")
  83. # PU_CTRL bit 7: AVDD source select
  84. pu_ctrl = scale.read_reg(REG_PU_CTRL)
  85. avdds = (pu_ctrl >> 7) & 0x1
  86. avdds_str = "Internal LDO" if avdds == 1 else "AVDD pin input"
  87. print(f" AVDD source: {avdds_str}")
  88. ctrl1 = scale.read_reg(REG_CTRL1)
  89. vldo = (ctrl1 >> 3) & 0b111
  90. vldo_map = {
  91. 0b111: "2.4V",
  92. 0b110: "2.7V",
  93. 0b101: "3.0V",
  94. 0b100: "3.3V",
  95. 0b011: "3.6V",
  96. 0b010: "3.9V",
  97. 0b001: "4.2V",
  98. 0b000: "4.5V",
  99. }
  100. vldo_str = vldo_map.get(vldo, f"Unknown ({vldo})")
  101. gain = ctrl1 & 0b111
  102. gain_map = {
  103. 0b000: "1x",
  104. 0b001: "2x",
  105. 0b010: "4x",
  106. 0b011: "8x",
  107. 0b100: "16x",
  108. 0b101: "32x",
  109. 0b110: "64x",
  110. 0b111: "128x",
  111. }
  112. gain_str = gain_map.get(gain, f"Unknown ({gain})")
  113. print(f" LDO setting (VLDO): {vldo_str}")
  114. print(f" Gain setting: {gain_str}")
  115. ctrl2 = scale.read_reg(REG_CTRL2)
  116. sps = (ctrl2 >> 4) & 0b111
  117. sps_map = {
  118. 0b000: "10 SPS",
  119. 0b001: "20 SPS",
  120. 0b010: "40 SPS",
  121. 0b011: "80 SPS",
  122. 0b100: "320 SPS",
  123. }
  124. sps_str = sps_map.get(sps, f"Unknown ({sps})")
  125. print(f" Sample rate: {sps_str}")
  126. adc = scale.read_reg(REG_ADC)
  127. chopper = (adc >> 4) & 0b11
  128. chopper_str = {0b00: "Enabled", 0b01: "Enabled", 0b10: "Enabled", 0b11: "Disabled"}.get(
  129. chopper, f"Unknown ({chopper})"
  130. )
  131. print(f" ADC chopper: {chopper_str}")
  132. pga = scale.read_reg(REG_PGA)
  133. low_esr = (pga >> 6) & 0x1
  134. low_esr_str = "Enabled" if low_esr == 0 else "Disabled"
  135. print(f" PGA low-ESR caps: {low_esr_str}")
  136. print("[2] Waiting for first reading...")
  137. for _ in range(200):
  138. if scale.data_ready():
  139. break
  140. time.sleep(0.010)
  141. else:
  142. print(" Timeout waiting for data ready")
  143. sys.exit(1)
  144. print(" Reading 10 samples (10 SPS = ~1 second)...")
  145. readings = []
  146. for i in range(10):
  147. # Wait for data ready
  148. for _ in range(200):
  149. if scale.data_ready():
  150. break
  151. time.sleep(0.010)
  152. raw = scale.read_raw()
  153. readings.append(raw)
  154. print(f" Sample {i + 1:2d}: {raw:>10d}")
  155. avg = sum(readings) / len(readings)
  156. spread = max(readings) - min(readings)
  157. print(f"\n Average: {avg:>10.0f}")
  158. print(f" Min: {min(readings):>10d}")
  159. print(f" Max: {max(readings):>10d}")
  160. print(f" Spread: {spread:>10d}")
  161. print("\n" + "=" * 60)
  162. print("Diagnostic complete!")
  163. print("=" * 60)
  164. except Exception as e:
  165. print(f"\nERROR: {e}")
  166. is_known_error = False
  167. if isinstance(e, OSError):
  168. if e.errno == 16: # Device or resource busy
  169. is_known_error = True
  170. print("\nI2C DEVICE BUSY (Errno 16): Another process is using the I2C bus.")
  171. print("This typically means the SpoolBuddy daemon is already reading the scale.")
  172. print("\nTo run this diagnostic, stop the daemon first:")
  173. print(" sudo systemctl stop bambuddy")
  174. print(" # Run diagnostic")
  175. print(" .../scale_diag.py")
  176. print(" # Restart daemon when done:")
  177. print(" sudo systemctl start bambuddy")
  178. elif e.errno == 121:
  179. is_known_error = True
  180. print("\nI2C NACK (Errno 121): the device did not acknowledge reads at 0x2A.")
  181. print("Check:")
  182. print(" - NAU7802 SDA/SCL are on the configured bus pins")
  183. print(" - 3.3V and GND are correct and stable")
  184. print(" - Sensor address is really 0x2A")
  185. print(" - No loose wire or swapped SDA/SCL")
  186. else:
  187. print(f"\nI2C Error (Errno {e.errno}): {e}")
  188. # Only print full traceback for unexpected errors
  189. if not is_known_error:
  190. import traceback
  191. traceback.print_exc()
  192. sys.exit(1)
  193. finally:
  194. scale.close()
  195. if __name__ == "__main__":
  196. main()