Files
swiftrplidar/Sources/SwiftRPLidar/SwiftRPLidar.swift
2020-07-09 20:20:59 +09:30

283 lines
7.8 KiB
Swift

import Foundation
struct Constants{
static let SYNC: UInt8 = 0xA5
static let SYNC2: UInt8 = 0x5A
static let GET_INFO: UInt8 = 0x50
static let GET_HEALTH: UInt8 = 0x52
static let STOP: UInt8 = 0x25
static let RESET: UInt8 = 0x40
static let SCAN: UInt8 = 0x20
static let FORCE_SCAN: UInt8 = 0x21
static let DESCRIPTOR_LEN = 7
static let INFO_LEN = 20
static let HEALTH_LEN = 3
static let INFO_TYPE: UInt8 = 4
static let HEALTH_TYPE: UInt8 = 6
static let SCAN_TYPE: UInt8 = 129
static let SET_PWM_BYTE: UInt8 = 0xF0
static let MAX_MOTOR_PWM = 1023
static let DEFAULT_MOTOR_PWM = 660
}
public enum HEALTH_STATUSES: UInt8 {
case GOOD = 0, WARNING, ERROR
}
func processScan(raw: Data) throws -> LidarScan {
let newScan = raw[0] & 0b1
let inversedNewScan = (raw[0] >> 1) & 0b1
let quality = raw[0] >> 2
if (newScan == inversedNewScan){
}
if ((raw[1] & 0b1) != 1) {
}
let angle = Float(raw[1] >> 1) + Float(raw[2] << 7) / 64
let distance = Float(raw[3]) + Float(raw[4] << 8) / 4
return LidarScan(newScan: newScan == 1, quality: quality, angle: angle, distance: distance)
}
public struct LidarScan{
var newScan: Bool
var quality: UInt8
var angle: Float
var distance: Float
}
public typealias MeasurementHandler = (_ scan: LidarScan) -> Bool
public typealias ScanHandler = (_ scans: [LidarScan]) -> Bool
public class SwiftRPLidar {
private var motor: Bool = false
private var serialPort: LidarSerial? = nil
private var motorRunning = false
public init(onPort serialPort: LidarSerial) throws {
self.serialPort = serialPort
try connect()
try startMotor()
}
deinit {
if(serialPort != nil){
serialPort?.closePort()
}
}
public func connect() throws {
disconnect()
try serialPort!.openPort()
}
public func disconnect(){
if(serialPort != nil){
// Need to close, SwiftSerial is blocking.
serialPort!.closePort()
}
}
public func startMotor() throws{
// A1
serialPort?.dtr = true
// A2
try self.setPwm(Constants.DEFAULT_MOTOR_PWM)
self.motorRunning = true
}
public func stopMotor() throws{
try setPwm(0)
serialPort?.dtr = false
motorRunning = false
}
public func setPwm(_ pwm: Int) throws{
assert(0 <= pwm && pwm <= Constants.MAX_MOTOR_PWM)
try self.sendPayloadCommand(Constants.SET_PWM_BYTE, payload: pwm.asData())
}
public func getInfo() throws -> (UInt8, UInt8, UInt8, String){
try sendCommand(Data([Constants.GET_HEALTH]))
let (dataSize, isSingle, dataType) = try readDescriptor()!
if (dataSize != Constants.INFO_LEN){
}
if(!isSingle){
}
if(dataType != Constants.INFO_TYPE){
}
let raw = try readResponse(dataSize)
let serialNumber = String(bytes: raw[4...], encoding: .ascii)!
return (raw[0], raw[2], raw[3], serialNumber)
}
public func getHealth() throws -> (HEALTH_STATUSES, UInt8){
try sendCommand(Constants.GET_HEALTH.asData())
let (dataSize, isSingle, dataType) = try readDescriptor()!
if (dataSize != Constants.HEALTH_LEN){
}
if (!isSingle){
}
if(dataType != Constants.HEALTH_TYPE){
}
let raw = try readResponse(dataSize)
let status = HEALTH_STATUSES(rawValue: raw[0])!
let errorCode = raw[1] << 8 + raw[2]
return (status, errorCode)
}
public func clearInput() throws{
try serialPort?.readData(ofLength: (serialPort?.inWaiting)!)
}
public func stop() throws{
try sendCommand(Constants.STOP.asData())
}
public func reset() throws{
try sendCommand(Constants.RESET.asData())
}
public func iterMeasurements(maxBufferMeasurements: Int = 500, _ onMeasure: MeasurementHandler) throws {
try startMotor()
let (status, errorCode) = try getHealth()
if status == .ERROR{
// Throw Exception
}
else if status == .WARNING {
}
try sendCommand(Constants.SCAN.asData())
let (dataSize, isSingle, dataType) = try readDescriptor()!
if dataSize != 5 {
}
if isSingle {
}
if dataType != Constants.SCAN_TYPE {
}
var read = true
// Need to check in waiting or something...
while read {
let raw = try readResponse(Int(dataSize))
if maxBufferMeasurements > 0 {
let dataInWaiting = serialPort?.inWaiting
if dataInWaiting! > maxBufferMeasurements {
print("Too many measurements in the input buffer. Clearing Buffer")
try serialPort?.readData(ofLength: dataInWaiting! / Int(dataSize) * Int(dataSize))
}
}
// TODO: Support cancelling of measurements. Would it already work though?
read = try onMeasure(processScan(raw: raw))
}
}
public func iterScans(maxBufferMeasurements: Int = 500, minLength: Int = 5, _ onScan: ScanHandler) throws {
var scan: [LidarScan] = []
var read = true
try iterMeasurements{ measurement in
if measurement.newScan {
if scan.count > minLength {
read = onScan(scan)
}
scan = []
}
if measurement.quality > 0 && measurement.distance > 0 {
scan.append(measurement)
}
return read
}
}
private func sendPayloadCommand(_ cmd: Int, payload: Data) throws{
let size = UInt8(payload.count)
var req = Constants.SYNC.asData() + cmd.asData() + size.asData() + payload
let checksum = calcChecksum(req)
req += checksum.asData()
try serialPort?.writeData(req)
}
private func sendPayloadCommand(_ cmd: UInt8, payload: Data) throws {
return try sendPayloadCommand(Int(cmd), payload: payload)
}
private func calcChecksum(_ data: Data) -> Int{
var checksum = 0;
data.forEach{ body in
checksum ^= Int(body)
}
return checksum
}
private func sendCommand(_ command: Data) throws{
var req = Constants.SYNC.asData()
req.append(command)
try serialPort?.writeData(req)
}
private func readDescriptor() throws -> (UInt8, Bool, UInt8)?{
guard let descriptor = try serialPort?.readData(ofLength: Constants.DESCRIPTOR_LEN) else {
return nil
}
if (descriptor.count != Constants.DESCRIPTOR_LEN){
return nil
}
else if (descriptor[0...1] != Data([Constants.SYNC, Constants.SYNC2])){
return nil
}
let isSingle = descriptor[descriptor.count - 2] == 0
return (descriptor[2], isSingle, descriptor[descriptor.count - 1])
}
private func readResponse(_ dataSize: Int) throws -> Data {
guard let data = try serialPort?.readData(ofLength: dataSize) else{
return Data()
}
if(data.count != dataSize){
return Data()
}
return data
}
private func readResponse(_ dataSize: UInt8) throws -> Data {
return try readResponse(Int(dataSize))
}
}
extension Int{
func asData() -> Data {
var int = self
return Data(bytes: &int, count: int / Int(UINT8_MAX))
}
}
extension UInt8 {
func asData() -> Data {
return Data([self])
}
}