Yet Another WebIOPi+
 All Classes Namespaces Files Functions Variables Macros Pages
vcnl4000.py
Go to the documentation of this file.
1 # Copyright 2013 Andreas Riegg
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 #
16 # Changelog
17 #
18 # 1.0 2013/02/24 Initial release. Luminosity is final. Proximity is good beta
19 # and a working coarse estimation for distance value.
20 #
21 
22 import time
23 from webiopi.devices.i2c import I2C
24 from webiopi.devices.sensor import Luminosity, Distance
25 from webiopi.utils.types import toint
26 from webiopi.utils.logger import debug
27 
29  REG_COMMAND = 0x80
30  REG_IR_LED_CURRENT = 0x83
31  REG_AMB_PARAMETERS = 0x84
32  REG_AMB_RESULT_HIGH = 0x85
33  REG_PROX_RESULT_HIGH = 0x87
34  REG_PROX_FREQUENCY = 0x89
35  REG_PROX_ADJUST = 0x8A
36 
37  VAL_MOD_TIMING_DEF = 129 # default from data sheet
38 
39  VAL_PR_FREQ_3M125HZ = 0
40  VAL_PR_FREQ_1M5625HZ = 1
41  VAL_PR_FREQ_781K25HZ = 2
42  VAL_PR_FREQ_390K625HZ = 3
43 
44  VAL_START_AMB = 1 << 4
45  VAL_START_PROX = 1 << 3
46 
47  VAL_INVALID = -1
48  VAL_NO_PROXIMITY = -1
49 
50  MASK_PROX_FREQUENCY = 0b00111111
51  MASK_IR_LED_CURRENT = 0b00111111
52  MASK_PROX_READY = 0b00100000
53  MASK_AMB_READY = 0b01000000
54 
55  def __init__(self, slave=0b0010011, current=20, frequency=781, prox_threshold=15, prox_cycles=10, cal_cycles= 5):
56  I2C.__init__(self, toint(slave))
57  self.setCurrent(toint(current))
58  self.setFrequency(toint(frequency))
59  self.prox_threshold = toint(prox_threshold)
60  self.prox_cycles = toint(prox_cycles)
61  self.cal_cycles = toint(cal_cycles)
64  time.sleep(0.001)
65  self.calibrate() # may have to be repeated from time to time or before every proximity measurement
66 
67  def __str__(self):
68  return "VCNL4000(slave=0x%02X)" % self.slave
69 
70  def __family__(self):
71  return [Luminosity.__family__(self), Distance.__family__(self)]
72 
75 
77  ambient_parameter_bytes = 1 << 7 | 1 << 3 | 5
78  # Parameter is set to
79  # -continuous conversion mode (bit 7)
80  # -auto offset compensation (bit 3)
81  # -averaging 32 samples (5)
82  self.writeRegister(self.REG_AMB_PARAMETERS, ambient_parameter_bytes)
83 
84  def calibrate(self):
85  self.offset = self.__measureOffset__()
86  debug ("VCNL4000: offset = %d" % (self.offset))
87  return self.offset
88 
89 
90  def setCurrent(self, current):
91  self.current = current
92  self.__setCurrent__()
93 
94 
95  def getCurrent(self):
96  return self.__getCurrent__()
97 
98  def setFrequency(self, frequency):
99  self.frequency = frequency
100  self.__setFrequency__()
101 
102  def getFrequency(self):
103  return self.__getFrequency__()
104 
105  def __setFrequency__(self):
106  if not self.frequency in [391, 781, 1563, 3125]:
107  raise ValueError("Frequency %d out of range [%d,%d,%d,,%d]" % (self.frequency, 391, 781, 1563, 3125))
108  if self.frequency == 391:
109  bits_frequency = self.VAL_PR_FREQ_390K625HZ
110  elif self.frequency == 781:
111  bits_frequency = self.VAL_PR_FREQ_781K25HZ
112  elif self.frequency == 1563:
113  bits_frequency = self.VAL_PR_FREQ_1M5625HZ
114  elif self.frequency == 3125:
115  bits_frequency = self.VAL_PR_FREQ_3M125HZ
116  self.writeRegister(self.REG_PROX_FREQUENCY, bits_frequency)
117  debug ("VCNL4000: new freq = %d" % (self.readRegister(self.REG_PROX_FREQUENCY)))
118 
119  def __getFrequency__(self):
120  bits_frequency = self.readRegister(self.REG_PROX_FREQUENCY) & self.MASK_PROX_FREQUENCY
121  if bits_frequency == self.VAL_PR_FREQ_390K625HZ:
122  f = 391
123  elif bits_frequency == self.VAL_PR_FREQ_781K25HZ:
124  f = 781
125  elif bits_frequency == self.VAL_PR_FREQ_1M5625HZ:
126  f = 1563
127  elif bits_frequency == self.VAL_PR_FREQ_3M125HZ:
128  f = 3125
129  else:
130  f = self.VAL_INVALID # indicates undefined
131  return f
132 
133  def __setCurrent__(self):
134  if not self.current in range(0,201):
135  raise ValueError("%d mA LED current out of range [%d..%d] mA" % (self.current, 0, 201))
136  self.writeRegister(self.REG_IR_LED_CURRENT, int(self.current / 10))
137  debug ("VCNL4000: new curr = %d" % (self.readRegister(self.REG_IR_LED_CURRENT)))
138 
139  def __getCurrent__(self):
140  bits_current = self.readRegister(self.REG_IR_LED_CURRENT) & self.MASK_IR_LED_CURRENT
141  return bits_current * 10
142 
143  def __getLux__(self):
144  self.writeRegister(self.REG_COMMAND, self.VAL_START_AMB)
145  while not (self.readRegister(self.REG_COMMAND) & self.MASK_AMB_READY):
146  time.sleep(0.001)
147  light_bytes = self.readRegisters(self.REG_AMB_RESULT_HIGH, 2)
148  light_word = light_bytes[0] << 8 | light_bytes[1]
149  return self.__calculateLux__(light_word)
150 
151  def __calculateLux__(self, light_word):
152  return (light_word + 3) * 0.25 # From VISHAY application note
153 
154  def __getMillimeter__(self):
155  success = 0
156  fail = 0
157  prox = 0
158  match_cycles = self.prox_cycles
159  while (fail < match_cycles) & (success < match_cycles):
160  real_counts = self.__readProximityCounts__() - self.offset
161  if real_counts > self.prox_threshold:
162  success += 1
163  prox += real_counts
164  else:
165  fail += 1
166  if fail == match_cycles:
167  return self.VAL_NO_PROXIMITY
168  else:
169  return self.__calculateMillimeter__(prox // match_cycles)
170 
171  def __calculateMillimeter__(self, raw_proximity_counts):
172  # According to chip spec the proximity counts are strong non-linear with distance and cannot be calculated
173  # with a direct formula. From experience found on web this chip is generally not suited for really exact
174  # distance calculations. This is a rough distance estimation lookup table for now. Maybe someone can
175  # provide a more exact approximation in the future.
176 
177  debug ("VCNL4000: prox real raw counts = %d" % (raw_proximity_counts))
178  if raw_proximity_counts >= 10000:
179  estimated_distance = 0
180  elif raw_proximity_counts >= 3000:
181  estimated_distance = 5
182  elif raw_proximity_counts >= 900:
183  estimated_distance = 10
184  elif raw_proximity_counts >= 300:
185  estimated_distance = 20
186  elif raw_proximity_counts >= 150:
187  estimated_distance = 30
188  elif raw_proximity_counts >= 75:
189  estimated_distance = 40
190  elif raw_proximity_counts >= 50:
191  estimated_distance = 50
192  elif raw_proximity_counts >= 25:
193  estimated_distance = 70
194  else:
195  estimated_distance = 100
196  return estimated_distance
197 
198  def __measureOffset__(self):
199  offset = 0
200  for unused in range(self.cal_cycles):
201  offset += self.__readProximityCounts__()
202  return offset // self.cal_cycles
203 
205  self.writeRegister(self.REG_COMMAND, self.VAL_START_PROX)
206  while not (self.readRegister(self.REG_COMMAND) & self.MASK_PROX_READY):
207  time.sleep(0.001)
208  proximity_bytes = self.readRegisters(self.REG_PROX_RESULT_HIGH, 2)
209  debug ("VCNL4000: prox raw value = %d" % (proximity_bytes[0] << 8 | proximity_bytes[1]))
210  return (proximity_bytes[0] << 8 | proximity_bytes[1])
211