--[[
   simulator for CAN EFI
   this can be used with a loopback cable between CAN1 and CAN2 to test CAN EFI Drivers
--]]

local driver2 = CAN.get_device2(25)
if not driver2 then
   gcs:send_text(0,string.format("EFISIM: Failed to load CAN driver"))
   return
end

local PARAM_TABLE_KEY = 13
local PARAM_TABLE_PREFIX = "EFISIM_"

-- bind a parameter to a variable given
function bind_param(name)
    local p = Parameter()
    assert(p:init(name), string.format('could not find %s parameter', name))
    return p
end

-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
    assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', name))
    return bind_param(PARAM_TABLE_PREFIX .. name)
end

assert(param:add_table(PARAM_TABLE_KEY, PARAM_TABLE_PREFIX, 2), 'could not add param table')

local EFISIM_TYPE  = bind_add_param('TYPE', 1, 0)
local EFISIM_RATE_HZ  = bind_add_param('RATE_HZ', 2, 100)

function get_time_sec()
   return millis():tofloat() * 0.001
end

local FRM_100 = uint32_t(0x80000100)
local FRM_101 = uint32_t(0x80000101)
local FRM_102 = uint32_t(0x80000102)
local FRM_104 = uint32_t(0x80000104)
local FRM_105 = uint32_t(0x80000105)
local FRM_106 = uint32_t(0x80000106)
local FRM_10A = uint32_t(0x8000010A)
local FRM_10C = uint32_t(0x8000010C)
local FRM_10D = uint32_t(0x8000010D)
local FRM_10F = uint32_t(0x8000010F)
local FRM_113 = uint32_t(0x80000113)
local FRM_114 = uint32_t(0x80000114)
local FRM_115 = uint32_t(0x80000115)

function put_u8(msg, ofs, v)
   msg:data(ofs,v&0xFF)
end

function put_u16_LE(msg, ofs, v)
   msg:data(ofs,v&0xFF)
   msg:data(ofs+1,v>>8)
end

function put_u32_LE(msg, ofs, v)
   msg:data(ofs+0,v&0xFF)
   msg:data(ofs+1,(v>>8)&0xFF)
   msg:data(ofs+2,(v>>16)&0xFF)
   msg:data(ofs+3,(v>>24)&0xFF)
end

function put_u16_BE(msg, ofs, v)
   msg:data(ofs+1,v&0xFF)
   msg:data(ofs,v>>8)
end

function put_u32_BE(msg, ofs, v)
   msg:data(ofs+3,v&0xFF)
   msg:data(ofs+2,(v>>8)&0xFF)
   msg:data(ofs+1,(v>>16)&0xFF)
   msg:data(ofs+0,(v>>24)&0xFF)
end

local rev_counter = 0

--[[
   send SkyPower data. Called at 100Hz
--]]
function send_SkyPower(driver)

   --local msg = CANFrame()
   local t = get_time_sec()

   local RPM = 1200 + math.floor(1000*math.sin(t))
   rev_counter = rev_counter + (RPM/60.0)*0.01

   -- 0x100
   local msg = CANFrame()
   msg:id(FRM_100)
   put_u16_LE(msg,0,RPM)
   put_u16_LE(msg,2,13*10) -- ignition angle
   put_u16_LE(msg,4,45*10) -- throttle angle
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x101
   msg = CANFrame()
   msg:id(FRM_101)
   put_u16_LE(msg,2,917) -- air pressure
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x102
   msg = CANFrame()
   msg:id(FRM_102)
   put_u16_LE(msg,0,7*10) -- ingition gap
   put_u16_LE(msg,2,270*10) -- injection angle
   put_u16_LE(msg,4,37000) -- injection time
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x104
   msg = CANFrame()
   msg:id(FRM_104)
   put_u16_LE(msg,0,math.floor(14.8*10)) -- supply voltage
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x105
   msg = CANFrame()
   msg:id(FRM_105)
   put_u16_LE(msg,0,172*10) -- engine temp head 1
   put_u16_LE(msg,2,65*10) -- air temp
   put_u16_LE(msg,4,320*10) -- exhaust temp
   put_u16_LE(msg,6,113*10) -- ecu temp
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x106
   msg = CANFrame()
   msg:id(FRM_106)
   put_u32_LE(msg,0,math.floor(rev_counter))
   put_u8(msg,4,math.floor(t))
   put_u8(msg,5,math.floor(t/60))
   put_u16_LE(msg,6,math.floor(t/3600))
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x10A
   msg = CANFrame()
   msg:id(FRM_10A)
   put_u16_LE(msg,6,72*10) -- target load
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x10C
   msg = CANFrame()
   msg:id(FRM_10C)
   put_u16_LE(msg,4,145*10) -- engine head temp 2
   put_u16_LE(msg,6,315*10) -- exhaust temp 2
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x10D
   msg = CANFrame()
   msg:id(FRM_10D)
   put_u16_LE(msg,4,72*10) -- current load
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x10F
   msg = CANFrame()
   msg:id(FRM_10F)
   put_u16_LE(msg,4,2*10) -- fuel consumption
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x113
   msg = CANFrame()
   msg:id(FRM_113)
   put_u16_LE(msg,2,1*10) -- gen amps
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x114
   msg = CANFrame()
   msg:id(FRM_114)
   put_u16_LE(msg,2,2400*10) -- gen RPM
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- 0x115
   msg = CANFrame()
   msg:id(FRM_115)
   put_u16_LE(msg,4,30*100) -- gen current
   msg:dlc(8)
   driver:write_frame(msg, 10000)
end

--[[
   send HFE data. Called at 100Hz
--]]
function send_HFE(driver)

   --local msg = CANFrame()
   local t = get_time_sec()

   local RPM = 1200 + math.floor(1000*math.sin(t))
   rev_counter = rev_counter + (RPM/60.0)*0.01

   -- fast telem
   local msg = CANFrame()
   local base_id = uint32_t(0x88000005)
   msg:id(base_id | 0x00000)
   put_u16_BE(msg,1,RPM)
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- slow telem0
   msg = CANFrame()
   msg:id(base_id | 0x10000)
   put_u16_BE(msg,5,101324/2) -- air pressure
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- slow telem1
   msg = CANFrame()
   msg:id(base_id | 0x20000)
   put_u8(msg,0,35) -- inlet air temp, signed, deg C
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- slow telem2
   msg = CANFrame()
   msg:id(base_id | 0x30000)
   put_u8(msg,1,math.floor((67+128)/1.5)) -- MAT
   put_u16_BE(msg,6,173) -- fuel flow in grams/hr
   msg:dlc(8)
   driver:write_frame(msg, 10000)
   

end

--[[
   send Halo6000 data. Called at 100Hz
--]]
function send_Halo6K(driver)

   --local msg = CANFrame()
   local t = get_time_sec()

   local RPM = 1200 + math.floor(1000*math.sin(t))
   rev_counter = rev_counter + (RPM/60.0)*0.01

   -- fast telem1
   local msg = CANFrame()
   msg:id(0x1c1)
   put_u16_LE(msg,0,RPM)
   msg:dlc(8)
   driver:write_frame(msg, 10000)

   -- fast telem2
   msg = CANFrame()
   msg:id(0x1c2)
   put_u16_LE(msg,0,math.floor(30.2*5))
   put_u16_LE(msg,2,math.floor(3.2*5)+200)
   msg:dlc(8)
   driver:write_frame(msg, 10000)
end

function update()
   if EFISIM_TYPE:get() == 1 then
      send_SkyPower(driver2)
   elseif EFISIM_TYPE:get() == 2 then
      send_HFE(driver2)
   elseif EFISIM_TYPE:get() == 3 then
      send_Halo6K(driver2)
   end
   return update, math.floor(1000/EFISIM_RATE_HZ:get())
end

gcs:send_text(0,string.format("EFISIM: loaded"))

return update()