mirror of https://github.com/ArduPilot/ardupilot
194 lines
5.9 KiB
Lua
194 lines
5.9 KiB
Lua
--[[
|
|
This script reads a SN-GCJA5 panasonic particle sensor on i2c
|
|
reading will be saved to data flash logs, CSV file and streamed as named value floats
|
|
|
|
Development of this script was sponsored by Cubepilot
|
|
|
|
the code is heavily based on the SparkFun arduino library
|
|
https://github.com/sparkfun/SparkFun_Particle_Sensor_SN-GCJA5_Arduino_Library
|
|
]]--
|
|
|
|
-- search for a index without a file, this stops us overwriting from a previous run
|
|
local index = 0
|
|
local file_name
|
|
while true do
|
|
file_name = string.format('Particle %i.csv',index)
|
|
local file = io.open(file_name)
|
|
local first_line = file:read(1) -- try and read the first character
|
|
io.close(file)
|
|
if first_line == nil then
|
|
break
|
|
end
|
|
index = index + 1
|
|
end
|
|
|
|
-- open file and make header
|
|
file = assert(io.open(file_name, 'w'), 'Could not make file :' .. file_name)
|
|
file:write('Lattitude (°), Longitude (°), Absolute Altitude (m), PM 1.0, PM 2.5, PM 10, count 0.5, count 1, count 2.5, count 5, count 7.5, count 10\n')
|
|
file:close()
|
|
|
|
-- load the i2c driver, bus 0
|
|
local sensor = i2c.get_device(0,0x33)
|
|
sensor:set_retries(10)
|
|
|
|
-- register names
|
|
local SNGCJA5_PM1_0 = 0x00
|
|
local SNGCJA5_PM2_5 = 0x04
|
|
local SNGCJA5_PM10 = 0x08
|
|
local SNGCJA5_PCOUNT_0_5 = 0x0C
|
|
local SNGCJA5_PCOUNT_1_0 = 0x0E
|
|
local SNGCJA5_PCOUNT_2_5 = 0x10
|
|
local SNGCJA5_PCOUNT_5_0 = 0x14
|
|
local SNGCJA5_PCOUNT_7_5 = 0x16
|
|
local SNGCJA5_PCOUNT_10 = 0x18
|
|
local SNGCJA5_STATE = 0x26
|
|
|
|
-- Reads two consecutive bytes from a given location
|
|
local function readRegister16(addr)
|
|
local lsb = sensor:read_registers(addr+0)
|
|
local msb = sensor:read_registers(addr+1)
|
|
if lsb and msb then
|
|
return msb << 8 | lsb
|
|
end
|
|
end
|
|
|
|
-- Reads four consecutive bytes from a given location
|
|
local function readRegister32(addr)
|
|
local ll = sensor:read_registers(addr+0)
|
|
local lh = sensor:read_registers(addr+1)
|
|
local hl = sensor:read_registers(addr+2)
|
|
local hh = sensor:read_registers(addr+3)
|
|
if ll and lh and hl and hh then
|
|
return (hh << 24) | (hl << 16) | (lh << 8) | (ll << 0)
|
|
end
|
|
end
|
|
|
|
local function getPM(pmRegister)
|
|
local count = readRegister32(pmRegister)
|
|
if count then
|
|
return count / 1000.0
|
|
end
|
|
end
|
|
|
|
function update() -- this is the loop which periodically runs
|
|
|
|
-- read status
|
|
local state = sensor:read_registers(SNGCJA5_STATE)
|
|
if not state then
|
|
gcs:send_text(0, "Failed to read particle sensor state")
|
|
return update, 10000
|
|
end
|
|
|
|
local Sensors = (state >> 6) & 3
|
|
local PD = (state >> 4) & 3
|
|
local LD = (state >> 2) & 3
|
|
local Fan = (state >> 0) & 3
|
|
|
|
-- report sensor errors
|
|
if Sensors ~= 0 then
|
|
if Sensors == 1 then
|
|
gcs:send_text(0, "particle sensor: One sensor or fan abnormal")
|
|
elseif Sensors == 2 then
|
|
gcs:send_text(0, "particle sensor: Two sensors or fan abnormal")
|
|
else
|
|
gcs:send_text(0, "particle sensor: Both sensors and fan abnormal")
|
|
end
|
|
return update, 10000
|
|
end
|
|
|
|
-- report photo diode errors
|
|
if PD ~= 0 then
|
|
if statusPD == 1 then
|
|
gcs:send_text(0, "particle sensor: Photo diode: Normal w/ software correction")
|
|
elseif statusPD == 2 then
|
|
gcs:send_text(0, "particle sensor: Photo diode: Abnormal, loss of function")
|
|
else
|
|
gcs:send_text(0, "particle sensor: Photo diode: Abnormal, with software correction")
|
|
end
|
|
return update, 10000
|
|
end
|
|
|
|
-- report laser diode errors
|
|
if LD ~= 0 then
|
|
if LD == 1 then
|
|
gcs:send_text(0, "particle sensor: Laser diode: Normal w/ software correction")
|
|
elseif LD == 2 then
|
|
gcs:send_text(0, "particle sensor: Laser diode: Abnormal, loss of function")
|
|
else
|
|
gcs:send_text(0, "particle sensor: Laser diode: Abnormal, with software correction")
|
|
end
|
|
return update, 10000
|
|
end
|
|
|
|
-- report fan errors
|
|
if Fan ~= 0 then
|
|
if Fan == 1 then
|
|
gcs:send_text(0, "particle sensor: Fan: Normal w/ software correction")
|
|
elseif Fan == 2 then
|
|
gcs:send_text(0, "particle sensor: Fan: In calibration")
|
|
else
|
|
gcs:send_text(0, "particle sensor: Fan: Abnormal, out of control")
|
|
end
|
|
return update, 10000
|
|
end
|
|
|
|
-- read mass density
|
|
local PM1_0 = getPM(SNGCJA5_PM1_0)
|
|
local PM2_5 = getPM(SNGCJA5_PM2_5)
|
|
local PM10 = getPM(SNGCJA5_PM10)
|
|
|
|
if (not PM1_0) or (not PM2_5) or (not PM10) then
|
|
gcs:send_text(0, "Failed to read particle sensor mass density")
|
|
return update, 10000
|
|
end
|
|
|
|
-- read particle counts
|
|
local PC0_5 = readRegister16(SNGCJA5_PCOUNT_0_5)
|
|
local PC1_0 = readRegister16(SNGCJA5_PCOUNT_1_0)
|
|
local PC2_5 = readRegister16(SNGCJA5_PCOUNT_2_5)
|
|
local PC5_0 = readRegister16(SNGCJA5_PCOUNT_5_0)
|
|
local PC7_5 = readRegister16(SNGCJA5_PCOUNT_7_5)
|
|
local PC10 = readRegister16(SNGCJA5_PCOUNT_10)
|
|
|
|
if (not PC0_5) or (not PC1_0) or (not PC2_5) or (not PC5_0) or (not PC7_5) or (not PC10) then
|
|
gcs:send_text(0, "Failed to read particle sensor counts")
|
|
return update, 10000
|
|
end
|
|
|
|
local lat = 0
|
|
local lng = 0
|
|
local alt = 0
|
|
|
|
-- try and get true position, but don't fail for no GPS lock
|
|
local position = ahrs:get_location()
|
|
if position then
|
|
lat = position:lat()*10^-7
|
|
lng = position:lng()*10^-7
|
|
alt = position:alt()*0.01
|
|
end
|
|
|
|
-- write to csv
|
|
file = io.open(file_name, 'a')
|
|
file:write(string.format('%0.8f, %0.8f, %0.2f, %0.4f, %0.4f, %0.4f, %i, %i, %i, %i, %i, %i\n',lat,lng,alt,PM1_0,PM2_5,PM10,PC0_5,PC1_0,PC2_5,PC5_0,PC7_5,PC10))
|
|
file:close()
|
|
|
|
-- save to data flash
|
|
logger.write('PART','PM1,PM2.5,PM10,Cnt0.5,Cnt1,Cnt2.5,Cnt5,Cnt7.5,Cnt10','fffffffff',PM1_0,PM2_5,PM10,PC0_5,PC1_0,PC2_5,PC5_0,PC7_5,PC10)
|
|
|
|
-- send to GCS
|
|
gcs:send_named_float('PM 1.0',PM1_0)
|
|
gcs:send_named_float('PM 2.5',PM2_5)
|
|
gcs:send_named_float('PM 10',PM10)
|
|
|
|
gcs:send_named_float('count 0.5',PC0_5)
|
|
gcs:send_named_float('count 1,',PC1_0)
|
|
gcs:send_named_float('count 2.5,',PC2_5)
|
|
gcs:send_named_float('count 5,',PC5_0)
|
|
gcs:send_named_float('count 7.5,',PC7_5)
|
|
gcs:send_named_float('count 10,',PC10)
|
|
|
|
return update, 1000 -- reschedules the loop, 1hz
|
|
end
|
|
|
|
return update() -- run immediately before starting to reschedule
|