ardupilot/libraries/AP_Scripting/examples/SN-GCJA5-particle-sensor.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