Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "official" support to DHT11 #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 97 additions & 87 deletions dht.swift
Original file line number Diff line number Diff line change
@@ -1,42 +1,50 @@
import Glibc
import Foundation
import SwiftyGPIO

enum DHTError: ErrorType {
case InvalidNumberOfPulses
case InvalidChecksum
enum DHTError: Error {
case invalidNumberOfPulses
case invalidChecksum
}

public class DHT {
var pin: GPIO
public enum SupportedSensor {
case dht11
case dht22
}

init(pin:GPIO) {
public class DHT {
private var pin: GPIO
private let sensor: SupportedSensor

init(pin: GPIO, for sensor: SupportedSensor) {
self.pin = pin
self.sensor = sensor
}

func setMaxPriority() {
var sched: sched_param = sched_param()
// Use FIFO scheduler with highest priority for the lowest chance of the kernel context switching.
sched.__sched_priority = sched_get_priority_max(SCHED_FIFO)
if (sched_setscheduler(0, SCHED_FIFO, &sched) != 0) {
print("ERROR")
}
// Use FIFO scheduler with highest priority for the lowest chance of the kernel context switching.
sched.__sched_priority = sched_get_priority_max(SCHED_FIFO)
if (sched_setscheduler(0, SCHED_FIFO, &sched) != 0) {
print("ERROR")
}
}

func setDefaultPriority() {
var sched: sched_param = sched_param()
// Go back to default scheduler with default 0 priority.
sched.__sched_priority = 0;
sched.__sched_priority = 0
sched_setscheduler(0, SCHED_OTHER, &sched)
}

func binaryStringForNumber(number: UInt8) -> String {
return self.binaryStringForNumber(Int(number))
func binaryString(for number: UInt8) -> String {
return binaryString(for: Int(number))
}

func binaryStringForNumber(number: Int) -> String {
let str = String(number, radix:2) //binary base
let padd = String(count: (8 - str.characters.count), repeatedValue: Character("0")) //repeat a character
return String(padd + str)
func binaryString(for number: Int) -> String {
let str = String(number, radix:2) //binary base
let padd = String(repeating: "0", count: (8 - str.characters.count)) //repeat a character
return String(padd + str)
}

func read(debug: Bool = false) throws -> (temperature: Double, humidity: Double) {
Expand All @@ -53,13 +61,13 @@ public class DHT {

// Store the count that each DHT bit pulse is low and high
// Making these arrays bigger tho cause sometimes we do catch the initial
// pulse, but sometimes usually not. yay for nonrealtime
var lowPulseTimes = [Int](count:kDHT_PULSES + 1, repeatedValue: 0)
var highPulseTimes = [Int](count:kDHT_PULSES + 1, repeatedValue: 0)

// pulse, but sometimes usually not. yay for nonrealtime
var lowPulseTimes = [Int](repeating: 0, count: kDHT_PULSES + 1)
var highPulseTimes = [Int](repeating: 0, count:kDHT_PULSES + 1)
// Set pin to output
self.pin.direction = .OUT

pin.direction = .OUT
var prevValue = 1
var lowpulse = 0
var highpulse = 0
Expand All @@ -68,7 +76,7 @@ public class DHT {
var endTime:timeval = timeval(tv_sec: 0, tv_usec: 0)

// The data sheet says the 'start signal' is having the microcontroller
// setting LOW for 1-10ms, then pull HIGH and wait for response from
// setting LOW for 1-10ms, then pull HIGH and wait for response from
// the AM2302 -- I found that if I did this, I never got good results,
// possibly because after sending LOW, I couldn't pull HIGH and
// switch to direction INPUT fast enough to start processing data?
Expand All @@ -78,104 +86,106 @@ public class DHT {
// I did find marginally better results by bumping up the priority of the thread
// but only marginally, it still fails ALOT -- A. LOT. Sampling every 5s I can
// usually get a good reading within the past 60s, many times more often than that
// but def not every time.
self.setMaxPriority()

// Set pin low
self.pin.value = 0

// but def not every time.
setMaxPriority()
// Set pin low
pin.value = 0
usleep(10000)

// see comment above
// self.pin.value = 1

// Set pin to in
self.pin.direction = .IN

pin.direction = .IN
// This is tough -- most other code I read had while loops, with
// timeouts -- i took a different approach and just spin in a for-loop
// constantly sampling the pin and comparing to the previous value.
// The end result is the same, but this way I'll never get stuck in a while-loop
// or have to mess with timeouts -- and this just makes more sense to me
gettimeofday(&startTime,nil)
gettimeofday(&startTime,nil)
for _ in 0..<5000000 {
if self.pin.value != prevValue {
gettimeofday(&endTime,nil)
let pulseTime = endTime.tv_usec - startTime.tv_usec
if pin.value != prevValue {
gettimeofday(&endTime,nil)
let pulseTime = endTime.tv_usec - startTime.tv_usec
if prevValue == 0 {
lowPulseTimes[lowpulse] = pulseTime
lowPulseTimes[lowpulse] = Int(pulseTime)
lowpulse = lowpulse + 1
} else {
highPulseTimes[highpulse] = pulseTime
highPulseTimes[highpulse] = Int(pulseTime)
highpulse = highpulse + 1
}
prevValue = self.pin.value
prevValue = pin.value
gettimeofday(&startTime,nil)
}
}

// Done with timing code, interpret the results
self.setDefaultPriority()

setDefaultPriority()
// This is a big hack -- if the timings are off, and i only get 40 pulses, I know the first one is low -- so regardless set it
// this helps us get better results -- but yeah...super hack
highPulseTimes[0] = 0


if debug {
print("LOW PULSE TIMINGS (\(lowpulse)): \(lowPulseTimes)")
print("HIGH PULSE TIMINGS (\(highpulse)): \(highPulseTimes)")
print("HIGH PULSE TIMINGS (\(highpulse)): \(highPulseTimes)")
}

guard highpulse >= 40 else { throw DHTError.InvalidNumberOfPulses }


guard highpulse >= 40 else { throw DHTError.invalidNumberOfPulses }
// Data sheet says 0bit pulses are ~28us, and 1bit pulses are ~70us
//
// this stuff with 'highSkip', are accounting for when we actually get the pulses
// during the startup signal. Usually, this is missed, but the array size and
// this offset account for that.
let highSkip = highpulse - 40
var data = [UInt8](count:5, repeatedValue: 0)
var prevIndex = 0
for i in highSkip..<(40+highSkip) {
let index = (i-highSkip) / 8
data[index] <<= 1
if prevIndex != index {
prevIndex = index
}
if highPulseTimes[i] >= 30 {
// One bit for long pulse
data[index] |= 1
}
// else zero bit for short pulse
}

// checksum is UPPER 8 BITS of the sum of the other values
let computedChecksum = (Int(data[0]) + Int(data[1]) + Int(data[2]) + Int(data[3])) & 0xFF
if debug {
print("Computed checksum: \(self.binaryStringForNumber(computedChecksum))(\(computedChecksum)) = (\(self.binaryStringForNumber(data[0]))(\(data[0])) + \(self.binaryStringForNumber(data[1]))(\(data[1])) + \(self.binaryStringForNumber(data[2]))(\(data[2])) + \(self.binaryStringForNumber(data[3]))(\(data[3]))) & 0xFF")
print(" Actual checksum: \(self.binaryStringForNumber(data[4]))(\(data[4]))")
// this offset account for that.
let highSkip = highpulse - 40
var data = [UInt8](repeating: 0, count: 5)
var prevIndex = 0
for i in highSkip..<(40+highSkip) {
let index = (i-highSkip) / 8
data[index] <<= 1
if prevIndex != index {
prevIndex = index
}
if highPulseTimes[i] >= 30 {
// One bit for long pulse
data[index] |= 1
}
// else zero bit for short pulse
}

// Verify checksum of received data
if (data[4] == UInt8(computedChecksum)) {
// DHT11
// humidity = Double(data[0])
// temperature = Double(data[2])

// only doing DHT22 for now
humidity = ((Double(data[0]) * 256.0) + Double(data[1])) / 10.0
temperature = ((Double(data[2] & 0x7F) * 256.0) + Double(data[3])) / 10.0
if ((data[2] & 0x80) != 0) {
temperature = temperature * -1.0
}
temperature = (temperature * 1.8) + 32
} else {
throw DHTError.InvalidChecksum
}

// checksum is UPPER 8 BITS of the sum of the other values
let computedChecksum = (Int(data[0]) + Int(data[1]) + Int(data[2]) + Int(data[3])) & 0xFF
if debug {
print("Computed checksum: \(binaryString(for: computedChecksum))(\(computedChecksum)) = (\(binaryString(for: data[0]))(\(data[0])) + \(binaryString(for: data[1]))(\(data[1])) + \(binaryString(for: data[2]))(\(data[2])) + \(binaryString(for: data[3]))(\(data[3]))) & 0xFF")
print(" Actual checksum: \(binaryString(for: data[4]))(\(data[4]))")
}

// Verify checksum of received data
if data[4] == UInt8(computedChecksum) {
switch sensor {
case .dht11:
humidity = Double(data[0])
temperature = Double(data[2])
default:
humidity = ((Double(data[0]) * 256.0) + Double(data[1])) / 10.0
temperature = ((Double(data[2] & 0x7F) * 256.0) + Double(data[3])) / 10.0
}

if ((data[2] & 0x80) != 0) {
temperature = temperature * -1.0
}
temperature = (temperature * 1.8) + 32
} else {
throw DHTError.invalidChecksum
}

if debug { print("----------------------------------")}

return (temperature,humidity)
}
}
}
73 changes: 36 additions & 37 deletions main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,45 @@ import Glibc
import Foundation

func initLCD() -> HD44780LCD {
let gpios = SwiftyGPIO.getGPIOsForBoard(.RaspberryPiRev2)
let gpios = SwiftyGPIO.GPIOs(for: .RaspberryPi2)
let lcd = HD44780LCD(rs:gpios[.P25]!,e:gpios[.P24]!,d7:gpios[.P22]!,d6:gpios[.P27]!,d5:gpios[.P17]!,d4:gpios[.P23]!,width:20,height:4)
return lcd
return lcd
}

let gpios = SwiftyGPIO.getGPIOsForBoard(.RaspberryPiRev2)
let lcd = initLCD()
let gpios = SwiftyGPIO.GPIOs(for: .RaspberryPi2)
let lcd = initLCD()
lcd.clearScreen()
lcd.cursorHome()

let dht = DHT(pin: gpios[.P4]!, for: .dht22)

var temperature = 0.0
var humidity = 0.0

// samples every 5s, outputs last good temp/humidty & the # of seconds since last good reading
var startTime:timeval = timeval(tv_sec: 0, tv_usec: 0)
var endTime:timeval = timeval(tv_sec: 0, tv_usec: 0)

gettimeofday(&startTime,nil)
while (true) {
do {
(temperature,humidity) = try dht.read(true)
gettimeofday(&startTime,nil)
print("temp: \(temperature) hum: \(humidity)")
} catch (DHTError.invalidNumberOfPulses) {
let errorMessage = "INVALID PULSES"
print(errorMessage)
} catch (DHTError.invalidChecksum) {
let errorMessage = "INVALID CHECKSUM"
print(errorMessage)
}

gettimeofday(&endTime,nil)

lcd.clearScreen()
lcd.cursorHome()

let dht = DHT(pin: gpios[.P4]!)

var temperature = 0.0
var humidity = 0.0

// samples every 5s, outputs last good temp/humidty & the # of seconds since last good reading
var startTime:timeval = timeval(tv_sec: 0, tv_usec: 0)
var endTime:timeval = timeval(tv_sec: 0, tv_usec: 0)

gettimeofday(&startTime,nil)
while (true) {
do {
(temperature,humidity) = try dht.read(true)
gettimeofday(&startTime,nil)
print("temp: \(temperature) hum: \(humidity)")
} catch (DHTError.InvalidNumberOfPulses) {
let errorMessage = "INVALID PULSES"
print(errorMessage)
} catch (DHTError.InvalidChecksum) {
let errorMessage = "INVALID CHECKSUM"
print(errorMessage)
}

gettimeofday(&endTime,nil)

lcd.clearScreen()
lcd.cursorHome()
lcd.printString(0,y:0,what:"Temp: \(temperature) \(endTime.tv_sec - startTime.tv_sec)s",usCharSet:true)
lcd.printString(0,y:1,what:"Humidity: \(humidity)",usCharSet:true)
lcd.printString(0,y:0,what:"Temp: \(temperature) \(endTime.tv_sec - startTime.tv_sec)s",usCharSet:true)
lcd.printString(0,y:1,what:"Humidity: \(humidity)",usCharSet:true)

sleep(5)
}

sleep(5)
}