├── .gitignore ├── INSTRUCTIONS.md ├── bower.json ├── descriptors ├── 1.4.txt ├── 1.5.txt ├── 1.6.txt └── versions.json ├── dist ├── descriptors.js ├── flybrix-common.js └── flybrix-common.min.js ├── gulpfile.js ├── karma.conf.js ├── package.json ├── src ├── calibration.js ├── cobs.js ├── commandLog.js ├── deviceConfig.js ├── firmwareVersion.js ├── led.js ├── module.js ├── rcData.js ├── serial.js └── serializationHandler.js └── test ├── calibration.js ├── cobs.js ├── commandLog.js ├── deviceConfig.js ├── led.js ├── rcData.js ├── serial.js └── serializationHandler.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/*** 2 | node_modules/*** 3 | coverage/*** 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | To grab dependencies, run two commands: 2 | ``` 3 | npm update 4 | bower update 5 | ``` 6 | 7 | To run tests, run: 8 | ``` 9 | karma start 10 | ``` 11 | 12 | If you want the changes you've made to apply to the minimized version, run: 13 | ``` 14 | gulp min 15 | ``` 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flybrix-common", 3 | "description": "Common AngularJS services for Flybrix", 4 | "private": true, 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "bower_components", 9 | "*.spec.js", 10 | "karma.conf.js", 11 | "test", 12 | "tests" 13 | ], 14 | "dependencies": { 15 | "flybrix-serialization": "flybrix/flybrix-serialization#v0.1.1", 16 | "angular": "^1.6.9" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /descriptors/1.4.txt: -------------------------------------------------------------------------------- 1 | Vector3f={x:f32,y:f32,z:f32}; 2 | PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32}; 3 | Version={major:u8,minor:u8,patch:u8}; 4 | ConfigID=u32; 5 | PcbTransform={orientation:Vector3f,translation:Vector3f}; 6 | MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]}; 7 | MagBias={offset:Vector3f}; 8 | ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]}; 9 | PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void}; 10 | PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,pid_bypass:PIDBypass}; 11 | StateParameters={state_estimation:[f32:2],enable:[f32:2]}; 12 | StatusFlag={/16/boot:void,mpu_fail:void,bmp_fail:void,rx_fail:void,idle:void,enabling:void,clear_mpu_bias:void,set_mpu_bias:void,fail_stability:void,fail_angle:void,enabled:void,battery_low:void,temp_warning:void,log_full:void,fail_other:void,override:void}; 13 | Color={red:u8,green:u8,blue:u8}; 14 | LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color}; 15 | LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool}; 16 | LEDStates=[/16/LEDStateCase:16]; 17 | LEDStatesFixed=[LEDStateCase:16]; 18 | DeviceName=s9; 19 | Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName}; 20 | ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName}; 21 | ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void}; 22 | Rotation={pitch:f32,roll:f32,yaw:f32}; 23 | PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32}; 24 | RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16}; 25 | State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32}; 26 | StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void}; 27 | AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void}; 28 | Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8}; 29 | DebugString={deprecated_mask:u32,message:s}; 30 | HistoryData=DebugString; 31 | Response={mask:u32,ack:u32}; 32 | -------------------------------------------------------------------------------- /descriptors/1.5.txt: -------------------------------------------------------------------------------- 1 | Vector3f={x:f32,y:f32,z:f32}; 2 | PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32}; 3 | Version={major:u8,minor:u8,patch:u8}; 4 | ConfigID=u32; 5 | PcbTransform={orientation:Vector3f,translation:Vector3f}; 6 | MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]}; 7 | MagBias={offset:Vector3f}; 8 | ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]}; 9 | PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void}; 10 | PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,thrust_gain:f32,pitch_gain:f32,roll_gain:f32,yaw_gain:f32,pid_bypass:PIDBypass}; 11 | StateParameters={state_estimation:[f32:2],enable:[f32:2]}; 12 | StatusFlag={/16/boot:void,mpu_fail:void,bmp_fail:void,rx_fail:void,idle:void,enabling:void,clear_mpu_bias:void,set_mpu_bias:void,fail_stability:void,fail_angle:void,enabled:void,battery_low:void,temp_warning:void,log_full:void,fail_other:void,override:void}; 13 | Color={red:u8,green:u8,blue:u8}; 14 | LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color}; 15 | LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool}; 16 | LEDStates=[/16/LEDStateCase:16]; 17 | LEDStatesFixed=[LEDStateCase:16]; 18 | DeviceName=s9; 19 | Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName}; 20 | ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName}; 21 | ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void,velocity_pid_parameters:void,inertial_bias:void}; 22 | Rotation={pitch:f32,roll:f32,yaw:f32}; 23 | PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32}; 24 | RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16}; 25 | State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32}; 26 | StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void}; 27 | AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void}; 28 | Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8}; 29 | DebugString={deprecated_mask:u32,message:s}; 30 | HistoryData=DebugString; 31 | Response={mask:u32,ack:u32}; 32 | -------------------------------------------------------------------------------- /descriptors/1.6.txt: -------------------------------------------------------------------------------- 1 | Vector3f={x:f32,y:f32,z:f32}; 2 | PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32}; 3 | Version={major:u8,minor:u8,patch:u8}; 4 | ConfigID=u32; 5 | PcbTransform={orientation:Vector3f,translation:Vector3f}; 6 | MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]}; 7 | MagBias={offset:Vector3f}; 8 | ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]}; 9 | PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void}; 10 | PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,thrust_gain:f32,pitch_gain:f32,roll_gain:f32,yaw_gain:f32,pid_bypass:PIDBypass}; 11 | StateParameters={state_estimation:[f32:2],enable:[f32:2]}; 12 | StatusFlag={/16/_0:void,_1:void,_2:void,no_signal:void,idle:void,arming:void,recording_sd:void,_7:void,loop_slow:void,_9:void,armed:void,battery_low:void,battery_critical:void,log_full:void,crash_detected:void,override:void}; 13 | Color={red:u8,green:u8,blue:u8}; 14 | LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color}; 15 | LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool}; 16 | LEDStates=[/16/LEDStateCase:16]; 17 | LEDStatesFixed=[LEDStateCase:16]; 18 | DeviceName=s9; 19 | InertialBias={accel:Vector3f,gyro:Vector3f}; 20 | VelocityPIDBypass={/8/forward_master:void,right_master:void,up_master:void,_unused_master:void,forward_slave:void,right_slave:void,up_slave:void,_unused_slave:void}; 21 | VelocityPIDParameters={forward_master:PIDSettings,right_master:PIDSettings,up_master:PIDSettings,forward_slave:PIDSettings,right_slave:PIDSettings,up_slave:PIDSettings,pid_bypass:VelocityPIDBypass}; 22 | Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName,velocity_pid_parameters:VelocityPIDParameters,inertial_bias:InertialBias}; 23 | ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName,velocity_pid_parameters:VelocityPIDParameters,inertial_bias:InertialBias}; 24 | ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void,velocity_pid_parameters:void,inertial_bias:void}; 25 | Rotation={pitch:f32,roll:f32,yaw:f32}; 26 | PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32}; 27 | RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16}; 28 | State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32}; 29 | StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void}; 30 | AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void}; 31 | Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8}; 32 | DebugString={deprecated_mask:u32,message:s}; 33 | HistoryData=DebugString; 34 | Response={mask:u32,ack:u32}; 35 | -------------------------------------------------------------------------------- /descriptors/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.4.0": "1.4.txt", 3 | "1.5.0": "1.5.txt", 4 | "1.5.1": "1.5.txt", 5 | "1.6.0": "1.6.txt" 6 | } 7 | -------------------------------------------------------------------------------- /dist/descriptors.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | descriptorsHandler.$inject = []; 4 | angular.module('flybrixCommon').factory('descriptorsHandler', descriptorsHandler); 5 | function descriptorsHandler() { 6 | var versions = {"1.4.0":"1.4.txt","1.5.0":"1.5.txt","1.5.1":"1.5.txt","1.6.0":"1.6.txt"}; 7 | var files = {"1.4.txt":"Vector3f={x:f32,y:f32,z:f32};PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32};Version={major:u8,minor:u8,patch:u8};ConfigID=u32;PcbTransform={orientation:Vector3f,translation:Vector3f};MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]};MagBias={offset:Vector3f};ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]};PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void};PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,pid_bypass:PIDBypass};StateParameters={state_estimation:[f32:2],enable:[f32:2]};StatusFlag={/16/boot:void,mpu_fail:void,bmp_fail:void,rx_fail:void,idle:void,enabling:void,clear_mpu_bias:void,set_mpu_bias:void,fail_stability:void,fail_angle:void,enabled:void,battery_low:void,temp_warning:void,log_full:void,fail_other:void,override:void};Color={red:u8,green:u8,blue:u8};LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color};LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool};LEDStates=[/16/LEDStateCase:16];LEDStatesFixed=[LEDStateCase:16];DeviceName=s9;Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName};ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName};ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void};Rotation={pitch:f32,roll:f32,yaw:f32};PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32};RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16};State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32};StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void};AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void};Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8};DebugString={deprecated_mask:u32,message:s};HistoryData=DebugString;Response={mask:u32,ack:u32};","1.5.txt":"Vector3f={x:f32,y:f32,z:f32};PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32};Version={major:u8,minor:u8,patch:u8};ConfigID=u32;PcbTransform={orientation:Vector3f,translation:Vector3f};MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]};MagBias={offset:Vector3f};ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]};PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void};PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,thrust_gain:f32,pitch_gain:f32,roll_gain:f32,yaw_gain:f32,pid_bypass:PIDBypass};StateParameters={state_estimation:[f32:2],enable:[f32:2]};StatusFlag={/16/boot:void,mpu_fail:void,bmp_fail:void,rx_fail:void,idle:void,enabling:void,clear_mpu_bias:void,set_mpu_bias:void,fail_stability:void,fail_angle:void,enabled:void,battery_low:void,temp_warning:void,log_full:void,fail_other:void,override:void};Color={red:u8,green:u8,blue:u8};LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color};LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool};LEDStates=[/16/LEDStateCase:16];LEDStatesFixed=[LEDStateCase:16];DeviceName=s9;Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName};ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName};ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void,velocity_pid_parameters:void,inertial_bias:void};Rotation={pitch:f32,roll:f32,yaw:f32};PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32};RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16};State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32};StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void};AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void};Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8};DebugString={deprecated_mask:u32,message:s};HistoryData=DebugString;Response={mask:u32,ack:u32};","1.6.txt":"Vector3f={x:f32,y:f32,z:f32};PIDSettings={kp:f32,ki:f32,kd:f32,integral_windup_guard:f32,d_filter_time:f32,setpoint_filter_time:f32,command_to_value:f32};Version={major:u8,minor:u8,patch:u8};ConfigID=u32;PcbTransform={orientation:Vector3f,translation:Vector3f};MixTable={fz:[i8:8],tx:[i8:8],ty:[i8:8],tz:[i8:8]};MagBias={offset:Vector3f};ChannelProperties={assignment:{thrust:u8,pitch:u8,roll:u8,yaw:u8,aux1:u8,aux2:u8},inversion:{/8/thrust:void,pitch:void,roll:void,yaw:void,aux1:void,aux2:void},midpoint:[u16:6],deadzone:[u16:6]};PIDBypass={/8/thrust_master:void,pitch_master:void,roll_master:void,yaw_master:void,thrust_slave:void,pitch_slave:void,roll_slave:void,yaw_slave:void};PIDParameters={thrust_master:PIDSettings,pitch_master:PIDSettings,roll_master:PIDSettings,yaw_master:PIDSettings,thrust_slave:PIDSettings,pitch_slave:PIDSettings,roll_slave:PIDSettings,yaw_slave:PIDSettings,thrust_gain:f32,pitch_gain:f32,roll_gain:f32,yaw_gain:f32,pid_bypass:PIDBypass};StateParameters={state_estimation:[f32:2],enable:[f32:2]};StatusFlag={/16/_0:void,_1:void,_2:void,no_signal:void,idle:void,arming:void,recording_sd:void,_7:void,loop_slow:void,_9:void,armed:void,battery_low:void,battery_critical:void,log_full:void,crash_detected:void,override:void};Color={red:u8,green:u8,blue:u8};LEDStateColors={right_front:Color,right_back:Color,left_front:Color,left_back:Color};LEDStateCase={status:StatusFlag,pattern:u8,colors:LEDStateColors,indicator_red:bool,indicator_green:bool};LEDStates=[/16/LEDStateCase:16];LEDStatesFixed=[LEDStateCase:16];DeviceName=s9;InertialBias={accel:Vector3f,gyro:Vector3f};VelocityPIDBypass={/8/forward_master:void,right_master:void,up_master:void,_unused_master:void,forward_slave:void,right_slave:void,up_slave:void,_unused_slave:void};VelocityPIDParameters={forward_master:PIDSettings,right_master:PIDSettings,up_master:PIDSettings,forward_slave:PIDSettings,right_slave:PIDSettings,up_slave:PIDSettings,pid_bypass:VelocityPIDBypass};Configuration={/16/version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStates,name:DeviceName,velocity_pid_parameters:VelocityPIDParameters,inertial_bias:InertialBias};ConfigurationFixed={version:Version,id:ConfigID,pcb_transform:PcbTransform,mix_table:MixTable,mag_bias:MagBias,channel:ChannelProperties,pid_parameters:PIDParameters,state_parameters:StateParameters,led_states:LEDStatesFixed,name:DeviceName,velocity_pid_parameters:VelocityPIDParameters,inertial_bias:InertialBias};ConfigurationFlag={/16/version:void,id:void,pcb_transform:void,mix_table:void,mag_bias:void,channel:void,pid_parameters:void,state_parameters:void,led_states:[//void:16],name:void,velocity_pid_parameters:void,inertial_bias:void};Rotation={pitch:f32,roll:f32,yaw:f32};PIDState={timestamp_us:u32,input:f32,setpoint:f32,p_term:f32,i_term:f32,d_term:f32};RcCommand={throttle:i16,pitch:i16,roll:i16,yaw:i16};State={/32/timestamp_us:u32,status:StatusFlag,v0_raw:u16,i0_raw:u16,i1_raw:u16,accel:Vector3f,gyro:Vector3f,mag:Vector3f,temperature:u16,pressure:u32,ppm:[i16:6],aux_chan_mask:u8,command:RcCommand,control:{fz:f32,tx:f32,ty:f32,tz:f32},pid_master_fz:PIDState,pid_master_tx:PIDState,pid_master_ty:PIDState,pid_master_tz:PIDState,pid_slave_fz:PIDState,pid_slave_tx:PIDState,pid_slave_ty:PIDState,pid_slave_tz:PIDState,motor_out:[i16:8],kinematics_angle:Rotation,kinematics_rate:Rotation,kinematics_altitude:f32,loop_count:u32};StateFields={/32/timestamp_us:void,status:void,v0_raw:void,i0_raw:void,i1_raw:void,accel:void,gyro:void,mag:void,temperature:void,pressure:void,ppm:void,aux_chan_mask:void,command:void,control:void,pid_master_fz:void,pid_master_tx:void,pid_master_ty:void,pid_master_tz:void,pid_slave_fz:void,pid_slave_tx:void,pid_slave_ty:void,pid_slave_tz:void,motor_out:void,kinematics_angle:void,kinematics_rate:void,kinematics_altitude:void,loop_count:void};AuxMask={//aux1_low:void,aux1_mid:void,aux1_high:void,aux2_low:void,aux2_mid:void,aux2_high:void};Command={/32/request_response:void,set_eeprom_data:ConfigurationFixed,reinit_eeprom_data:void,request_eeprom_data:void,request_enable_iteration:u8,motor_override_speed_0:u16,motor_override_speed_1:u16,motor_override_speed_2:u16,motor_override_speed_3:u16,motor_override_speed_4:u16,motor_override_speed_5:u16,motor_override_speed_6:u16,motor_override_speed_7:u16,set_command_override:bool,set_state_mask:StateFields,set_state_delay:u16,set_sd_write_delay:u16,set_led:{pattern:u8,color_right:Color,color_left:Color,indicator_red:bool,indicator_green:bool},set_serial_rc:{enabled:bool,command:RcCommand,aux_mask:AuxMask},set_card_recording_state:{/8/record_to_card:void,lock_recording_state:void},set_partial_eeprom_data:Configuration,reinit_partial_eeprom_data:ConfigurationFlag,req_partial_eeprom_data:ConfigurationFlag,req_card_recording_state:void,set_partial_temporary_config:Configuration,set_command_sources:{/8/serial:void,radio:void},set_calibration:{enabled:bool,mode:u8},set_autopilot_enabled:bool,set_usb_mode:u8};DebugString={deprecated_mask:u32,message:s};HistoryData=DebugString;Response={mask:u32,ack:u32};"}; 8 | return { versions: versions, files: files }; 9 | } 10 | }()); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var uglify = require('gulp-uglify'); 3 | var concat = require('gulp-concat'); 4 | var plumber = require('gulp-plumber'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var templateCache = require('gulp-angular-templatecache'); 7 | var fs = require('fs'); 8 | var packageInfo = require('./package.json'); 9 | var descriptors = require('./descriptors/versions.json'); 10 | 11 | var htmlFileList = ['src/**/*.html']; 12 | var jsFileList = ['src/**/*.js']; 13 | var libraryFiles = ['src/module.js', 'dist/descriptors.js', 'dist/templates.js'].concat(jsFileList); 14 | var watchedFileList = htmlFileList.concat(jsFileList); 15 | 16 | gulp.task('templates', function() { 17 | return gulp.src(htmlFileList) 18 | .pipe(plumber()) 19 | .pipe(templateCache('templates.js', { 20 | module: packageInfo.angularModuleName, 21 | })) 22 | .pipe(gulp.dest('./dist/')); 23 | }); 24 | 25 | gulp.task('descriptors', function() { 26 | var files = {}; 27 | Object.keys(descriptors).forEach(function(key) { 28 | var file = descriptors[key]; 29 | if (file in files) { 30 | return; 31 | } 32 | files[file] = fs.readFileSync('./descriptors/' + file, 'utf8').replace(/\s/g,''); 33 | }); 34 | var fileContents = "(function () {\n" + 35 | " 'use strict';\n" + 36 | " descriptorsHandler.$inject = [];\n" + 37 | " angular.module('flybrixCommon').factory('descriptorsHandler', descriptorsHandler);\n" + 38 | " function descriptorsHandler() {\n" + 39 | " var versions = " + JSON.stringify(descriptors) + ";\n" + 40 | " var files = " + JSON.stringify(files) + ";\n" + 41 | " return { versions: versions, files: files };\n" + 42 | " }\n" + 43 | "}());"; 44 | fs.writeFileSync('dist/descriptors.js', fileContents); 45 | }); 46 | 47 | gulp.task('build-nomin', ['descriptors', 'templates'], function() { 48 | return gulp.src(libraryFiles) 49 | .pipe(plumber()) 50 | .pipe(sourcemaps.init()) 51 | .pipe(concat(packageInfo.name + '.js')) 52 | .pipe(sourcemaps.write()) 53 | .pipe(gulp.dest('./dist/')); 54 | }); 55 | 56 | gulp.task('build-min', ['descriptors', 'templates'], function() { 57 | return gulp.src(libraryFiles) 58 | .pipe(plumber()) 59 | .pipe(sourcemaps.init()) 60 | .pipe(concat(packageInfo.name + '.min.js')) 61 | .pipe(uglify()) 62 | .pipe(sourcemaps.write()) 63 | .pipe(gulp.dest('./dist/')); 64 | }); 65 | 66 | gulp.task('build', ['build-nomin', 'build-min']); 67 | 68 | gulp.task('watch', ['build'], function() { 69 | return gulp.watch(watchedFileList, ['build']); 70 | }); 71 | 72 | gulp.task('default', ['watch']); 73 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Apr 20 2018 20:50:18 GMT+0200 (Central European Summer Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | './bower_components/angular/angular.js', 19 | './node_modules/angular-mocks/angular-mocks.js', 20 | './bower_components/flybrix-serialization/dist/flybrix-serialization.js', 21 | 'dist/flybrix-common.js', 22 | 'test/*.js' 23 | ], 24 | 25 | 26 | // list of files / patterns to exclude 27 | exclude: [ 28 | ], 29 | 30 | 31 | // preprocess matching files before serving them to the browser 32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | preprocessors: { 34 | }, 35 | 36 | 37 | // test results reporter to use 38 | // possible values: 'dots', 'progress' 39 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 40 | reporters: ['progress'], 41 | 42 | 43 | // web server port 44 | port: 9876, 45 | 46 | 47 | // enable / disable colors in the output (reporters and logs) 48 | colors: true, 49 | 50 | 51 | // level of logging 52 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 53 | logLevel: config.LOG_INFO, 54 | 55 | 56 | // enable / disable watching file and executing tests whenever any file changes 57 | autoWatch: true, 58 | 59 | 60 | // start these browsers 61 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 62 | browsers: ['Chrome'], 63 | 64 | 65 | // Continuous Integration mode 66 | // if true, Karma captures browsers, runs the tests and exits 67 | singleRun: false, 68 | 69 | // Concurrency level 70 | // how many browser should be started simultaneous 71 | concurrency: Infinity 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flybrix-common", 3 | "angularModuleName": "flybrixCommon", 4 | "scripts": { 5 | "build": "gulp build", 6 | "watch": "gulp watch" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "^1.6.10", 10 | "gulp": "^3.9.1", 11 | "gulp-angular-templatecache": "^2.2.0", 12 | "gulp-concat": "^2.6.1", 13 | "gulp-plumber": "^1.2.0", 14 | "gulp-sourcemaps": "^2.6.4", 15 | "gulp-uglify": "^3.0.0", 16 | "jasmine-core": "^3.1.0", 17 | "karma": "^2.0.2", 18 | "karma-chrome-launcher": "^2.2.0", 19 | "karma-jasmine": "^1.1.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/calibration.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('calibration', calibration); 5 | 6 | calibration.$inject = ['commandLog', 'serial']; 7 | 8 | function calibration(commandLog, serial) { 9 | return { 10 | magnetometer: magnetometer, 11 | accelerometer: { 12 | flat: calibrateAccelerometer.bind(null, 'flat', 0), 13 | forward: calibrateAccelerometer.bind(null, 'lean forward', 1), 14 | back: calibrateAccelerometer.bind(null, 'lean back', 2), 15 | right: calibrateAccelerometer.bind(null, 'lean right', 3), 16 | left: calibrateAccelerometer.bind(null, 'lean left', 4), 17 | }, 18 | finish: finish, 19 | }; 20 | 21 | function magnetometer() { 22 | commandLog("Calibrating magnetometer bias"); 23 | return serial.sendStructure('Command', { 24 | request_response: true, 25 | set_calibration: { 26 | enabled: true, 27 | mode: 0, 28 | }, 29 | }, false); 30 | } 31 | 32 | function calibrateAccelerometer(poseDescription, poseId) { 33 | commandLog("Calibrating gravity for pose: " + poseDescription); 34 | return serial.sendStructure('Command', { 35 | request_response: true, 36 | set_calibration: { 37 | enabled: true, 38 | mode: poseId + 1, 39 | }, 40 | }, false); 41 | } 42 | 43 | function finish() { 44 | commandLog("Finishing calibration"); 45 | return serial.sendStructure('Command', { 46 | request_response: true, 47 | set_calibration: { 48 | enabled: false, 49 | mode: 0, 50 | }, 51 | }, false); 52 | } 53 | } 54 | }()); 55 | -------------------------------------------------------------------------------- /src/cobs.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('cobs', cobs); 5 | 6 | function cobs() { 7 | return { 8 | Reader: Reader, 9 | encode: encode, 10 | }; 11 | } 12 | 13 | 14 | function Reader(capacity) { 15 | if (capacity === undefined) { 16 | capacity = 2000; 17 | } 18 | this.N = capacity; 19 | this.buffer = new Uint8Array(capacity); 20 | this.ready_for_new_message = true; 21 | this.buffer_length = 0; 22 | } 23 | 24 | function cobsDecode(reader) { 25 | var src_ptr = 0; 26 | var dst_ptr = 0; 27 | var leftover_length = 0; 28 | var append_zero = false; 29 | while (reader.buffer[src_ptr]) { 30 | if (!leftover_length) { 31 | if (append_zero) 32 | reader.buffer[dst_ptr++] = 0; 33 | leftover_length = reader.buffer[src_ptr++] - 1; 34 | append_zero = leftover_length < 0xFE; 35 | } else { 36 | --leftover_length; 37 | reader.buffer[dst_ptr++] = reader.buffer[src_ptr++]; 38 | } 39 | } 40 | 41 | return leftover_length ? 0 : dst_ptr; 42 | } 43 | 44 | Reader.prototype.readBytes = function(data, onSuccess, onError) { 45 | for (var i = 0; i < data.length; i++) { 46 | var c = data[i]; 47 | if (this.ready_for_new_message) { 48 | // first byte of a new message 49 | this.ready_for_new_message = false; 50 | this.buffer_length = 0; 51 | } 52 | 53 | this.buffer[this.buffer_length++] = c; 54 | 55 | if (c) { 56 | if (this.buffer_length === this.N) { 57 | // buffer overflow, probably due to errors in data 58 | onError('overflow', 'buffer overflow in COBS decoding'); 59 | this.ready_for_new_message = true; 60 | } 61 | continue; 62 | } 63 | 64 | this.buffer_length = cobsDecode(this); 65 | var failed_decode = (this.buffer_length === 0); 66 | if (failed_decode) { 67 | this.buffer[0] = 1; 68 | } 69 | var j; 70 | for (j = 1; j < this.buffer_length; ++j) { 71 | this.buffer[0] ^= this.buffer[j]; 72 | } 73 | if (this.buffer[0] === 0) { // check sum is correct 74 | this.ready_for_new_message = true; 75 | if (this.buffer_length > 0) { 76 | onSuccess(this.buffer.slice(1, this.buffer_length)); 77 | } else { 78 | onError('short', 'Too short packet'); 79 | } 80 | } else { // bad checksum 81 | this.ready_for_new_message = true; 82 | var bytes = ""; 83 | var message = ""; 84 | for (j = 0; j < this.buffer_length; j++) { 85 | bytes += this.buffer[j] + ","; 86 | message += String.fromCharCode(this.buffer[j]); 87 | } 88 | if (failed_decode) { 89 | onError('frame', 'Unexpected ending of packet'); 90 | } else { 91 | var msg = 'BAD CHECKSUM (' + this.buffer_length + 92 | ' bytes)' + bytes + message; 93 | onError('checksum', msg); 94 | } 95 | } 96 | } 97 | }; 98 | 99 | function encode(buf) { 100 | var retval = 101 | new Uint8Array(Math.floor((buf.byteLength * 255 + 761) / 254)); 102 | var len = 1; 103 | var pos_ctr = 0; 104 | for (var i = 0; i < buf.length; ++i) { 105 | if (retval[pos_ctr] == 0xFE) { 106 | retval[pos_ctr] = 0xFF; 107 | pos_ctr = len++; 108 | retval[pos_ctr] = 0; 109 | } 110 | var val = buf[i]; 111 | ++retval[pos_ctr]; 112 | if (val) { 113 | retval[len++] = val; 114 | } else { 115 | pos_ctr = len++; 116 | retval[pos_ctr] = 0; 117 | } 118 | } 119 | return retval.subarray(0, len).slice().buffer; 120 | }; 121 | }()); 122 | -------------------------------------------------------------------------------- /src/commandLog.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('commandLog', commandLog); 5 | 6 | commandLog.$inject = ['$q']; 7 | 8 | function commandLog($q) { 9 | var messages = []; 10 | var responder = $q.defer(); 11 | 12 | var service = log; 13 | service.log = log; 14 | service.clearSubscribers = clearSubscribers; 15 | service.onMessage = onMessage; 16 | service.read = read; 17 | 18 | return service; 19 | 20 | function log(message) { 21 | if (message !== undefined) { 22 | messages.push(message); 23 | responder.notify(read()); 24 | } 25 | } 26 | 27 | function read() { 28 | return messages; 29 | } 30 | 31 | function clearSubscribers() { 32 | responder = $q.defer(); 33 | } 34 | 35 | function onMessage(callback) { 36 | return responder.promise.then(undefined, undefined, callback); 37 | } 38 | } 39 | }()); 40 | -------------------------------------------------------------------------------- /src/deviceConfig.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('deviceConfig', deviceConfig); 5 | 6 | deviceConfig.$inject = ['serial', 'commandLog', 'firmwareVersion', 'serializationHandler']; 7 | 8 | function deviceConfig(serial, commandLog, firmwareVersion, serializationHandler) { 9 | var config; 10 | 11 | var configCallback = function() { 12 | commandLog('No callback defined for receiving configurations!'); 13 | }; 14 | 15 | var loggingCallback = function() { 16 | commandLog( 17 | 'No callback defined for receiving logging state!' + 18 | ' Callback arguments: (isLogging, isLocked, delay)'); 19 | }; 20 | 21 | serial.addOnReceiveCallback(function(messageType, message) { 22 | if (messageType !== 'Command') { 23 | return; 24 | } 25 | if ('set_eeprom_data' in message) { 26 | updateConfigFromRemoteData(message.set_eeprom_data); 27 | } 28 | if ('set_partial_eeprom_data' in message) { 29 | updateConfigFromRemoteData(message.set_partial_eeprom_data); 30 | } 31 | if (('set_card_recording_state' in message) && ('set_sd_write_delay' in message)) { 32 | var card_rec_state = message.set_card_recording_state; 33 | var sd_write_delay = message.set_sd_write_delay; 34 | loggingCallback(card_rec_state.record_to_card, card_rec_state.lock_recording_state, sd_write_delay); 35 | } 36 | }); 37 | 38 | function getDesiredVersion() { 39 | return firmwareVersion.desired(); 40 | } 41 | 42 | 43 | function request() { 44 | var handlers = firmwareVersion.serializationHandler(); 45 | commandLog('Requesting current configuration data...'); 46 | return serial.sendStructure('Command', { 47 | request_response: true, 48 | req_partial_eeprom_data: handlers.ConfigurationFlag.empty(), 49 | }, false); 50 | } 51 | 52 | function reinit() { 53 | commandLog('Setting factory default configuration data...'); 54 | return serial.sendStructure('Command', { 55 | request_response: true, 56 | reinit_eeprom_data: true, 57 | }, false) 58 | .then( 59 | function() { 60 | return request(); 61 | }, 62 | function(reason) { 63 | commandLog( 64 | 'Request for factory reset failed: ' + reason); 65 | }); 66 | } 67 | 68 | function send(newConfig) { 69 | return sendConfig({ config: newConfig, temporary: false, requestUpdate: true }); 70 | } 71 | 72 | function sendConfig(properties) { 73 | var handlers = firmwareVersion.serializationHandler(); 74 | var mask = properties.mask || handlers.ConfigurationFlag.empty(); 75 | var newConfig = properties.config || config; 76 | var requestUpdate = properties.requestUpdate || false; 77 | var message = { 78 | request_response: true, 79 | }; 80 | if (properties.temporary) { 81 | message.set_partial_temporary_config = newConfig; 82 | mask = { set_partial_temporary_config: mask }; 83 | } else { 84 | message.set_partial_eeprom_data = newConfig; 85 | mask = { set_partial_eeprom_data: mask }; 86 | } 87 | return serial.sendStructure('Command', message, true, mask).then(function() { 88 | if (requestUpdate) { 89 | request(); 90 | } 91 | }); 92 | } 93 | 94 | function updateConfigFromRemoteData(configChanges) { 95 | //commandLog('Received config!'); 96 | config = serializationHandler.updateFields(config, configChanges); 97 | var version = [config.version.major, config.version.minor, config.version.patch]; 98 | firmwareVersion.set(version); 99 | if (!firmwareVersion.supported()) { 100 | commandLog('Received an unsupported configuration!'); 101 | commandLog( 102 | 'Found version: ' + version[0] + '.' + version[1] + '.' + version[2] + 103 | ' --- Newest version: ' + 104 | firmwareVersion.desiredKey() ); 105 | } else { 106 | commandLog( 107 | 'Received configuration data (v' + 108 | version[0] + '.' + version[1] + '.' + version[2] +')'); 109 | configCallback(); 110 | } 111 | } 112 | 113 | function setConfigCallback(callback) { 114 | configCallback = callback; 115 | } 116 | 117 | function setLoggingCallback(callback) { 118 | loggingCallback = callback; 119 | } 120 | 121 | function getConfig() { 122 | return config; 123 | } 124 | 125 | config = firmwareVersion.serializationHandler().Configuration.empty(); 126 | 127 | return { 128 | request: request, 129 | reinit: reinit, 130 | send: send, 131 | sendConfig: sendConfig, 132 | getConfig: getConfig, 133 | setConfigCallback: setConfigCallback, 134 | setLoggingCallback: setLoggingCallback, 135 | getDesiredVersion: getDesiredVersion, 136 | }; 137 | } 138 | }()); 139 | -------------------------------------------------------------------------------- /src/firmwareVersion.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('firmwareVersion', firmwareVersion); 5 | 6 | firmwareVersion.$inject = ['serializationHandler']; 7 | 8 | function firmwareVersion(serializationHandler) { 9 | var version = [0, 0, 0]; 10 | var key = '0.0.0'; 11 | 12 | var newestVersion = serializationHandler.getNewestVersion(); 13 | 14 | var desired = [newestVersion.major, newestVersion.minor, newestVersion.patch]; 15 | var desiredKey = desired[0].toString() + '.' + desired[1].toString() + '.' + desired[2].toString(); 16 | 17 | var defaultSerializationHandler = serializationHandler.getHandler(desiredKey); 18 | var currentSerializationHandler = defaultSerializationHandler; 19 | 20 | return { 21 | set: function(value) { 22 | version = value; 23 | key = value.join('.'); 24 | currentSerializationHandler = 25 | serializationHandler.getHandler(desiredKey) || defaultSerializationHandler; 26 | }, 27 | get: function() { 28 | return version; 29 | }, 30 | key: function() { 31 | return key; 32 | }, 33 | supported: function() { 34 | return !!serializationHandler.getHandler(key); 35 | }, 36 | desired: function() { 37 | return desired; 38 | }, 39 | desiredKey: function() { 40 | return desiredKey; 41 | }, 42 | serializationHandler: function() { 43 | return currentSerializationHandler; 44 | }, 45 | }; 46 | } 47 | 48 | }()); 49 | -------------------------------------------------------------------------------- /src/led.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('led', led); 5 | 6 | led.$inject = ['deviceConfig', 'firmwareVersion']; 7 | 8 | function led(deviceConfig, firmwareVersion) { 9 | var LedPatterns = { 10 | NO_OVERRIDE: 0, 11 | FLASH: 1, 12 | BEACON: 2, 13 | BREATHE: 3, 14 | ALTERNATE: 4, 15 | SOLID: 5, 16 | }; 17 | 18 | var keys = ['right_front', 'right_back', 'left_front', 'left_back']; 19 | var colors = {}; 20 | 21 | keys.forEach(function(key) { 22 | colors[key] = { 23 | red: 0, 24 | green: 0, 25 | blue: 0, 26 | } 27 | }); 28 | 29 | var ledState = { 30 | status: 65535, 31 | pattern: LedPatterns.SOLID, 32 | colors: colors, 33 | indicator_red: false, 34 | indicator_green: false, 35 | }; 36 | 37 | var configPart = { led_states: [ledState] }; 38 | 39 | function set( 40 | color_rf, color_rb, color_lf, color_lb, pattern, red, green) { 41 | ledState.status = firmwareVersion.serializationHandler().StatusFlag.empty(); 42 | if (pattern > 0 && pattern < 6) { 43 | ledState.pattern = pattern; 44 | } 45 | [color_rf, color_rb, color_lf, color_lb].forEach(function( 46 | color, idx) { 47 | if (!color) { 48 | return; 49 | } 50 | var v = colors[keys[idx]]; 51 | v.red = color.red; 52 | v.green = color.green; 53 | v.blue = color.blue; 54 | }); 55 | if (red !== undefined) { 56 | ledState.indicator_red = red; 57 | } 58 | if (green !== undefined) { 59 | ledState.indicator_green = green; 60 | } 61 | 62 | apply(); 63 | } 64 | 65 | function setSimple(red, green, blue) { 66 | var color = {red: red || 0, green: green || 0, blue: blue || 0}; 67 | set(color, color, color, color, LedPatterns.SOLID); 68 | } 69 | 70 | function apply() { 71 | deviceConfig.sendConfig({ 72 | config: configPart, 73 | temporary: true, 74 | }); 75 | } 76 | 77 | return { 78 | set: set, 79 | setSimple: setSimple, 80 | patterns: LedPatterns, 81 | }; 82 | } 83 | 84 | }()); 85 | -------------------------------------------------------------------------------- /src/module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon', []); 5 | }()); 6 | -------------------------------------------------------------------------------- /src/rcData.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('rcData', rcData); 5 | 6 | rcData.$inject = ['serial']; 7 | 8 | function rcData(serial) { 9 | var AUX = { 10 | LOW: 0, 11 | MID: 1, 12 | HIGH: 2, 13 | }; 14 | var auxNames = ['low', 'mid', 'high']; 15 | 16 | var throttle = -1; 17 | var pitch = 0; 18 | var roll = 0; 19 | var yaw = 0; 20 | // defaults to high -- low is enabling; high is disabling 21 | var aux1 = AUX.HIGH; 22 | // defaults to ?? -- need to check transmitter behavior 23 | var aux2 = AUX.HIGH; 24 | 25 | var urgent = true; 26 | 27 | return { 28 | setThrottle: setThrottle, 29 | setPitch: setPitch, 30 | setRoll: setRoll, 31 | setYaw: setYaw, 32 | setAux1: setAux1, 33 | setAux2: setAux2, 34 | getThrottle: getThrottle, 35 | getPitch: getPitch, 36 | getRoll: getRoll, 37 | getYaw: getYaw, 38 | getAux1: getAux1, 39 | getAux2: getAux2, 40 | AUX: AUX, 41 | send: send, 42 | forceNextSend: forceNextSend, 43 | }; 44 | 45 | function send() { 46 | if (!urgent && serial.busy()) { 47 | return; 48 | } 49 | urgent = false; 50 | 51 | var command = {}; 52 | 53 | // invert pitch and roll 54 | var throttle_threshold = 55 | -0.8; // keep bottom 10% of throttle stick to mean 'off' 56 | command.throttle = constrain( 57 | (throttle - throttle_threshold) * 4095 / 58 | (1 - throttle_threshold), 59 | 0, 4095); 60 | command.pitch = 61 | constrain(-(applyDeadzone(pitch, 0.1)) * 4095 / 2, -2047, 2047); 62 | command.roll = 63 | constrain((applyDeadzone(roll, 0.1)) * 4095 / 2, -2047, 2047); 64 | command.yaw = 65 | constrain(-(applyDeadzone(yaw, 0.1)) * 4095 / 2, -2047, 2047); 66 | 67 | var aux_mask = {}; 68 | // aux1_low, aux1_mid, aux1_high, and same with aux2 69 | aux_mask['aux1_' + auxNames[aux1]] = true; 70 | aux_mask['aux2_' + auxNames[aux2]] = true; 71 | 72 | return serial.sendStructure('Command', { 73 | request_response: true, 74 | set_serial_rc: { 75 | enabled: true, 76 | command: command, 77 | aux_mask: aux_mask, 78 | }, 79 | }, false); 80 | } 81 | 82 | function setThrottle(v) { 83 | throttle = v; 84 | } 85 | 86 | function setPitch(v) { 87 | pitch = v; 88 | } 89 | 90 | function setRoll(v) { 91 | roll = v; 92 | } 93 | 94 | function setYaw(v) { 95 | yaw = v; 96 | } 97 | 98 | function setAux1(v) { 99 | aux1 = Math.max(0, Math.min(2, v)); 100 | } 101 | 102 | function setAux2(v) { 103 | aux2 = Math.max(0, Math.min(2, v)); 104 | } 105 | 106 | function getThrottle() { 107 | return throttle; 108 | } 109 | 110 | function getPitch() { 111 | return pitch; 112 | } 113 | 114 | function getRoll() { 115 | return roll; 116 | } 117 | 118 | function getYaw() { 119 | return yaw; 120 | } 121 | 122 | function getAux1() { 123 | return aux1; 124 | } 125 | 126 | function getAux2() { 127 | return aux2; 128 | } 129 | 130 | function forceNextSend() { 131 | urgent = true; 132 | } 133 | 134 | function constrain(x, xmin, xmax) { 135 | return Math.max(xmin, Math.min(x, xmax)); 136 | } 137 | 138 | function applyDeadzone(value, deadzone) { 139 | if (value > deadzone) { 140 | return value - deadzone; 141 | } 142 | if (value < -deadzone) { 143 | return value + deadzone; 144 | } 145 | return 0; 146 | } 147 | } 148 | }()); 149 | -------------------------------------------------------------------------------- /src/serial.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('flybrixCommon').factory('serial', serial); 5 | 6 | serial.$inject = ['$timeout', '$q', 'cobs', 'commandLog', 'firmwareVersion', 'serializationHandler']; 7 | 8 | function serial($timeout, $q, cobs, commandLog, firmwareVersion, serializationHandler) { 9 | var MessageType = { 10 | State: 0, 11 | Command: 1, 12 | DebugString: 3, 13 | HistoryData: 4, 14 | Protocol: 128, 15 | Response: 255, 16 | }; 17 | 18 | var acknowledges = []; 19 | var backend = new Backend(); 20 | 21 | var onReceiveListeners = []; 22 | 23 | var cobsReader = new cobs.Reader(10000); 24 | var bytesHandler = undefined; 25 | 26 | function Backend() { 27 | } 28 | 29 | Backend.prototype.busy = function() { 30 | return false; 31 | }; 32 | 33 | Backend.prototype.send = function(data) { 34 | commandLog('No "send" defined for serial backend'); 35 | }; 36 | 37 | Backend.prototype.onRead = function(data) { 38 | commandLog('No "onRead" defined for serial backend'); 39 | }; 40 | 41 | var MessageTypeInversion = []; 42 | Object.keys(MessageType).forEach(function(key) { 43 | MessageTypeInversion[MessageType[key]] = key; 44 | }); 45 | 46 | addOnReceiveCallback(function(messageType, message) { 47 | if (messageType === 'Response') { 48 | acknowledge(message.mask, message.ack); 49 | } else if (messageType === 'Protocol') { 50 | var data = message.response; 51 | if (data) { 52 | serializationHandler.addHandler(data.version, data.structure); 53 | } 54 | } 55 | }); 56 | 57 | return { 58 | busy: busy, 59 | sendStructure: sendStructure, 60 | setBackend: setBackend, 61 | addOnReceiveCallback: addOnReceiveCallback, 62 | removeOnReceiveCallback: removeOnReceiveCallback, 63 | setBytesHandler: setBytesHandler, 64 | handlePostConnect: handlePostConnect, 65 | Backend: Backend, 66 | }; 67 | 68 | function setBackend(v) { 69 | backend = v; 70 | backend.onRead = read; 71 | } 72 | 73 | function handlePostConnect() { 74 | return requestFirmwareVersion(); 75 | } 76 | 77 | function requestFirmwareVersion() { 78 | return sendStructure('Command', { 79 | request_response: true, 80 | req_partial_eeprom_data: { 81 | version: true, 82 | }, 83 | }); 84 | } 85 | 86 | function sendStructure(messageType, data, log_send, extraMask) { 87 | if (messageType === 'State') { 88 | data = processStateOutput(data); 89 | } 90 | var handlers = firmwareVersion.serializationHandler(); 91 | 92 | var response = $q.defer(); 93 | if (!(messageType in MessageType)) { 94 | response.reject('Message type "' + messageType + 95 | '" not supported by app, supported message types are:' + 96 | Object.keys(MessageType).join(', ')); 97 | return response.promise; 98 | } 99 | if (!(messageType in handlers)) { 100 | response.reject('Message type "' + messageType + 101 | '" not supported by firmware, supported message types are:' + 102 | Object.keys(handlers).join(', ')); 103 | return response.promise; 104 | } 105 | var typeCode = MessageType[messageType]; 106 | var handler = handlers[messageType]; 107 | 108 | var buffer = new Uint8Array(handler.byteCount); 109 | 110 | var serializer = new serializationHandler.Serializer(new DataView(buffer.buffer)); 111 | handler.encode(serializer, data, extraMask); 112 | var mask = handler.maskArray(data, extraMask); 113 | if (mask.length < 5) { 114 | mask = (mask[0] << 0) | (mask[1] << 8) | (mask[2] << 16) | (mask[3] << 24); 115 | } 116 | 117 | var dataLength = serializer.index; 118 | 119 | var output = new Uint8Array(dataLength + 3); 120 | output[0] = output[1] = typeCode; 121 | for (var idx = 0; idx < dataLength; ++idx) { 122 | output[0] ^= output[idx + 2] = buffer[idx]; 123 | } 124 | output[dataLength + 2] = 0; 125 | 126 | acknowledges.push({ 127 | mask: mask, 128 | response: response, 129 | }); 130 | 131 | $timeout(function() { 132 | backend.send(new Uint8Array(cobs.encode(output))); 133 | }, 0); 134 | 135 | if (log_send) { 136 | commandLog('Sending command ' + typeCode); 137 | } 138 | 139 | return response.promise; 140 | } 141 | 142 | function busy() { 143 | return backend.busy(); 144 | } 145 | 146 | function setBytesHandler(handler) { 147 | bytesHandler = handler; 148 | } 149 | 150 | function read(data) { 151 | if (bytesHandler) 152 | bytesHandler(data, processData); 153 | else 154 | cobsReader.readBytes(data, processData, reportIssues); 155 | } 156 | 157 | function reportIssues(issue, text) { 158 | commandLog('COBS decoding failed (' + issue + '): ' + text); 159 | } 160 | 161 | function addOnReceiveCallback(callback) { 162 | onReceiveListeners = onReceiveListeners.concat([callback]); 163 | } 164 | 165 | function removeOnReceiveCallback(callback) { 166 | onReceiveListeners = onReceiveListeners.filter(function(cb) { 167 | return cb !== callback; 168 | }); 169 | } 170 | 171 | function acknowledge(mask, value) { 172 | while (acknowledges.length > 0) { 173 | var v = acknowledges.shift(); 174 | if (v.mask ^ mask) { 175 | v.response.reject('Missing ACK'); 176 | continue; 177 | } 178 | var relaxedMask = mask; 179 | relaxedMask &= ~1; 180 | if (relaxedMask ^ value) { 181 | v.response.reject('Request was not fully processed'); 182 | break; 183 | } 184 | v.response.resolve(); 185 | break; 186 | } 187 | } 188 | 189 | function processData(bytes) { 190 | var messageType = MessageTypeInversion[bytes[0]]; 191 | var handler = firmwareVersion.serializationHandler()[messageType]; 192 | if (!messageType || !handler) { 193 | commandLog('Illegal message type passed from firmware'); 194 | return; 195 | } 196 | try { 197 | var serializer = new serializationHandler.Serializer(new DataView(bytes.buffer, 1)); 198 | var message = handler.decode(serializer); 199 | } catch (err) { 200 | commandLog('Unrecognized message format received'); 201 | } 202 | if (messageType === 'State') { 203 | message = processStateInput(message); 204 | } 205 | onReceiveListeners.forEach(function(listener) { 206 | listener(messageType, message); 207 | }); 208 | } 209 | 210 | var last_timestamp_us = 0; 211 | 212 | function processStateInput(state) { 213 | state = Object.assign({}, state); 214 | var serial_update_rate_Hz = 0; 215 | 216 | if ('timestamp_us' in state) { 217 | state.serial_update_rate_estimate = 1000000 / (state.timestamp_us - last_timestamp_us); 218 | last_timestamp_us = state.timestamp_us; 219 | } 220 | if ('temperature' in state) { 221 | state.temperature /= 100.0; // temperature given in Celsius * 100 222 | } 223 | if ('pressure' in state) { 224 | state.pressure /= 256.0; // pressure given in (Q24.8) format 225 | } 226 | 227 | return state; 228 | } 229 | 230 | function processStateOutput(state) { 231 | state = Object.assign({}, state); 232 | if ('temperature' in state) { 233 | state.temperature *= 100.0; 234 | } 235 | if ('pressure' in state) { 236 | state.pressure *= 256.0; 237 | } 238 | return state; 239 | } 240 | } 241 | }()); 242 | -------------------------------------------------------------------------------- /src/serializationHandler.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | serializationHandler.$inject = ['descriptorsHandler']; 5 | 6 | angular.module('flybrixCommon').factory('serializationHandler', serializationHandler); 7 | 8 | function serializationHandler(descriptorsHandler) { 9 | var handlerCache = {}; 10 | 11 | var newestVersion = { major: 0, minor: 0, patch: 0 }; 12 | 13 | function isNewerVersion(version) { 14 | if (version.major !== newestVersion.major) { 15 | return version.major > newestVersion.major; 16 | } 17 | if (version.minor !== newestVersion.minor) { 18 | return version.minor > newestVersion.minor; 19 | } 20 | return version.patch > newestVersion.patch; 21 | } 22 | 23 | function versionToString(version) { 24 | return version.major.toString() + '.' + version.minor.toString() + '.' + version.patch.toString(); 25 | } 26 | 27 | function stringToVersion(version) { 28 | var parts = version.split('.'); 29 | return { 30 | major: parseInt(parts[0]), 31 | minor: parseInt(parts[1]), 32 | patch: parseInt(parts[2]), 33 | }; 34 | } 35 | 36 | function addHandler(version, structure) { 37 | if (isNewerVersion(version)) { 38 | newestVersion = { 39 | major: version.major, 40 | minor: version.minor, 41 | patch: version.patch, 42 | }; 43 | } 44 | handlerCache[versionToString(version)] = FlybrixSerialization.parse(structure); 45 | } 46 | 47 | function copyHandler(version, srcVersion) { 48 | if (isNewerVersion(version)) { 49 | newestVersion = { 50 | major: version.major, 51 | minor: version.minor, 52 | patch: version.patch, 53 | }; 54 | } 55 | handlerCache[versionToString(version)] = handlerCache[versionToString(srcVersion)]; 56 | } 57 | 58 | var descVersions = descriptorsHandler.versions; 59 | var descFiles = descriptorsHandler.files; 60 | var descReverseMap = {}; 61 | Object.keys(descVersions).forEach(function(key) { 62 | var vers = stringToVersion(key); 63 | var filename = descVersions[key]; 64 | if (filename in descReverseMap) { 65 | copyHandler(vers, descReverseMap[filename]) 66 | } else { 67 | filename = descVersions[key]; 68 | addHandler(vers, descFiles[filename]); 69 | descReverseMap[filename] = vers; 70 | } 71 | }); 72 | 73 | function updateFields(target, source) { 74 | if (source instanceof Array) { 75 | return updateFieldsArray(target, source); 76 | } else if (source instanceof Object) { 77 | return updateFieldsObject(target, source); 78 | } else { 79 | return (source === null || source === undefined) ? target : source; 80 | } 81 | } 82 | 83 | function updateFieldsObject(target, source) { 84 | var result = {}; 85 | Object.keys(target).forEach(function (key) { 86 | result[key] = updateFields(target[key], source[key]); 87 | }); 88 | Object.keys(source).forEach(function (key) { 89 | if (key in result) { 90 | return; 91 | } 92 | result[key] = updateFields(target[key], source[key]); 93 | }); 94 | return result; 95 | } 96 | 97 | function updateFieldsArray(target, source) { 98 | var length = Math.max(target.length, source.length); 99 | var result = []; 100 | for (var idx = 0; idx < length; ++idx) { 101 | result.push(updateFields(target[idx], source[idx])); 102 | } 103 | return result; 104 | } 105 | 106 | return { 107 | Serializer: FlybrixSerialization.Serializer, 108 | getHandler: function (firmware) { 109 | return handlerCache[firmware]; 110 | }, 111 | getNewestVersion: function () { 112 | return newestVersion; 113 | }, 114 | addHandler: addHandler, 115 | copyHandler: copyHandler, 116 | updateFields: updateFields, 117 | }; 118 | } 119 | 120 | }()); 121 | -------------------------------------------------------------------------------- /test/calibration.js: -------------------------------------------------------------------------------- 1 | describe('Calibration service', function() { 2 | 3 | var calibration; 4 | var serial; 5 | var cobs; 6 | var commandLog; 7 | var $timeout; 8 | var $rootScope; 9 | var backend; 10 | 11 | beforeEach(angular.mock.module('flybrixCommon')); 12 | 13 | beforeEach(inject(function(_calibration_, _serial_, _cobs_, _commandLog_, _$timeout_, _$rootScope_) { 14 | calibration = _calibration_; 15 | serial = _serial_; 16 | cobs = _cobs_; 17 | commandLog = _commandLog_; 18 | $timeout = _$timeout_; 19 | $rootScope = _$rootScope_; 20 | })); 21 | 22 | beforeEach(function() { 23 | backend = new serial.Backend(); 24 | serial.setBackend(backend); 25 | }); 26 | 27 | it('exists', function() { 28 | expect(calibration).toBeDefined(); 29 | }); 30 | 31 | describe('Finishing calibration', function() { 32 | it('exists', function() { 33 | expect(calibration.finish).toBeDefined(); 34 | }); 35 | 36 | it('sends right data', function(done) { 37 | backend.send = function(val) { 38 | var decoder = new cobs.Reader(); 39 | decoder.readBytes(val, function(data) { 40 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 0, 0])); 41 | done(); 42 | }); 43 | }; 44 | calibration.finish(); 45 | $timeout.flush(); 46 | }); 47 | }); 48 | 49 | describe('Magnetometer calibration', function() { 50 | it('exists', function() { 51 | expect(calibration.magnetometer).toBeDefined(); 52 | }); 53 | 54 | it('sends right data', function(done) { 55 | backend.send = function(val) { 56 | var decoder = new cobs.Reader(); 57 | decoder.readBytes(val, function(data) { 58 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 0])); 59 | done(); 60 | }); 61 | }; 62 | calibration.magnetometer(); 63 | $timeout.flush(); 64 | }); 65 | }); 66 | 67 | describe('Accelerometer calibration', function() { 68 | it('exists', function() { 69 | expect(calibration.accelerometer).toBeDefined(); 70 | }); 71 | 72 | it('has methods for each pose', function() { 73 | expect(calibration.accelerometer.flat).toBeDefined(); 74 | expect(calibration.accelerometer.forward).toBeDefined(); 75 | expect(calibration.accelerometer.back).toBeDefined(); 76 | expect(calibration.accelerometer.right).toBeDefined(); 77 | expect(calibration.accelerometer.left).toBeDefined(); 78 | }); 79 | 80 | it('sends right data for flat', function(done) { 81 | backend.send = function(val) { 82 | var decoder = new cobs.Reader(); 83 | decoder.readBytes(val, function(data) { 84 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 1])); 85 | done(); 86 | }); 87 | }; 88 | calibration.accelerometer.flat(); 89 | $timeout.flush(); 90 | }); 91 | 92 | it('sends right data for forward', function(done) { 93 | backend.send = function(val) { 94 | var decoder = new cobs.Reader(); 95 | decoder.readBytes(val, function(data) { 96 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 2])); 97 | done(); 98 | }); 99 | }; 100 | calibration.accelerometer.forward(); 101 | $timeout.flush(); 102 | }); 103 | 104 | it('sends right data for back', function(done) { 105 | backend.send = function(val) { 106 | var decoder = new cobs.Reader(); 107 | decoder.readBytes(val, function(data) { 108 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 3])); 109 | done(); 110 | }); 111 | }; 112 | calibration.accelerometer.back(); 113 | $timeout.flush(); 114 | }); 115 | 116 | it('sends right data for right', function(done) { 117 | backend.send = function(val) { 118 | var decoder = new cobs.Reader(); 119 | decoder.readBytes(val, function(data) { 120 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 4])); 121 | done(); 122 | }); 123 | }; 124 | calibration.accelerometer.right(); 125 | $timeout.flush(); 126 | }); 127 | 128 | it('sends right data for left', function(done) { 129 | backend.send = function(val) { 130 | var decoder = new cobs.Reader(); 131 | decoder.readBytes(val, function(data) { 132 | expect(data).toEqual(new Uint8Array([1, 1, 0, 0, 4, 1, 5])); 133 | done(); 134 | }); 135 | }; 136 | calibration.accelerometer.left(); 137 | $timeout.flush(); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/cobs.js: -------------------------------------------------------------------------------- 1 | describe('COBS service', function() { 2 | var cobs; 3 | 4 | beforeEach(angular.mock.module('flybrixCommon')); 5 | 6 | beforeEach(inject(function(_cobs_) { 7 | cobs = _cobs_; 8 | })); 9 | 10 | it('exists', function() { 11 | expect(cobs).toBeDefined(); 12 | }); 13 | 14 | describe('.encode()', function() { 15 | it('exists', function() { 16 | expect(cobs.encode).toBeDefined(); 17 | }); 18 | 19 | it('encodes empty input data', function() { 20 | expect(new Uint8Array(cobs.encode(new Uint8Array([0])))) 21 | .toEqual(new Uint8Array([1, 0])); 22 | }); 23 | 24 | it('encodes non-zero data', function() { 25 | expect(new Uint8Array(cobs.encode(new Uint8Array([2, 7, 44, 0])))) 26 | .toEqual(new Uint8Array([4, 2, 7, 44, 0])); 27 | }); 28 | 29 | it('encodes data with zeros', function() { 30 | expect(new Uint8Array(cobs.encode( 31 | new Uint8Array([2, 4, 0, 7, 44, 33, 12, 5, 0, 16, 0])))) 32 | .toEqual( 33 | new Uint8Array([3, 2, 4, 6, 7, 44, 33, 12, 5, 2, 16, 0])); 34 | }); 35 | 36 | it('encodes long non-zero blocks', function() { 37 | var input = new Uint8Array(351); 38 | var output = new Uint8Array(353); 39 | 40 | for (var i = 0; i < 350; ++i) { 41 | var n = i % 100 + 1; 42 | input[i] = n; 43 | output[i + (i > 253 ? 2 : 1)] = n; 44 | } 45 | 46 | input[350] = 0; 47 | output[352] = 0; 48 | output[0] = 255; 49 | output[255] = 97; 50 | 51 | var result = new Uint8Array(cobs.encode(input)); 52 | expect(result.length).toEqual(353); 53 | expect(result).toEqual(output); 54 | }); 55 | 56 | it('encodes long packets', function() { 57 | var input = new Uint8Array(351); 58 | var output = new Uint8Array(352); 59 | 60 | for (var i = 0; i < 351; ++i) { 61 | var n = i % 2; 62 | input[i] = n; 63 | output[i] = 1 + n; 64 | } 65 | 66 | input[350] = 0; 67 | output[351] = 0; 68 | 69 | var result = new Uint8Array(cobs.encode(input)); 70 | expect(result.length).toEqual(352); 71 | expect(result).toEqual(output); 72 | }); 73 | }); 74 | 75 | describe('.Reader()', function() { 76 | var reader; 77 | 78 | beforeEach(function() { 79 | reader = new cobs.Reader(); 80 | }); 81 | 82 | it('exists', function() { 83 | expect(cobs.Reader).toBeDefined(); 84 | }); 85 | 86 | it('is constructor', function() { 87 | expect(reader).toBeDefined(); 88 | }); 89 | 90 | it('accepts capacity argument', function() { 91 | expect(new cobs.Reader(553).buffer.length).toEqual(553); 92 | expect(new cobs.Reader(3172).buffer.length).toEqual(3172); 93 | }); 94 | 95 | describe('.readBytes()', function() { 96 | it('exists', function() { 97 | expect(reader.readBytes).toBeDefined(); 98 | }); 99 | 100 | it('handles trivial packet', function(done) { 101 | reader.readBytes( 102 | [1, 1, 1, 1, 1, 1, 1, 0], function(data) { 103 | expect(data).toEqual(new Uint8Array([0, 0, 0, 0, 0])); 104 | done(); 105 | }, errFail); 106 | }); 107 | 108 | it('handles full packets', function(done) { 109 | reader.readBytes( 110 | [8, 236, 3, 0x12, 0xA4, 0x03, 0x1A, 13, 2, 77, 0], 111 | function(data) { 112 | expect(data).toEqual(new Uint8Array([3, 0x12, 0xA4, 0x03, 0x1A, 13, 0, 77])); 113 | done(); 114 | }, 115 | errFail); 116 | }); 117 | 118 | it('handles partial packets', function(done) { 119 | reader.readBytes([8, 236, 3, 0x12], cbFail, errFail); 120 | reader.readBytes( 121 | [0xA4, 0x03, 0x1A, 13, 2, 77, 0], 122 | function(data) { 123 | expect(data).toEqual(new Uint8Array([3, 0x12, 0xA4, 0x03, 0x1A, 13, 0, 77])); 124 | done(); 125 | }, 126 | errFail); 127 | }); 128 | 129 | it('handles consecutive packets', function(done) { 130 | var counter = 0; 131 | var exp_data = [ 132 | new Uint8Array([3, 0x12, 0xA4, 0x03, 0x1A, 13, 0, 77]), 133 | new Uint8Array([7, 0x44, 0x22, 0x11, 0x77, 72, 55, 0, 18, 14, 0, 23]), 134 | ]; 135 | reader.readBytes( 136 | [ 137 | 8, 236, 3, 0x12, 0xA4, 0x03, 0x1A, 13, 2, 77, 0, 138 | 9, 115, 7, 0x44, 0x22, 0x11, 0x77, 72, 55, 3, 18, 14, 2, 23, 0, 139 | ], 140 | function(data) { 141 | expect(data).toEqual(exp_data[counter]); 142 | if (++counter === 2) { 143 | done(); 144 | } 145 | }, 146 | errFail); 147 | }); 148 | 149 | it('handles large non-zero packets', function(done) { 150 | var data = new Uint8Array(303); 151 | var output = new Uint8Array(299); 152 | for (var i = 0; i < 303; ++i) { 153 | data[i] = 5; 154 | } 155 | for (var i = 0; i < 299; ++i) { 156 | output[i] = 5; 157 | } 158 | data[0] = 255; 159 | data[255] = 47; 160 | data[302] = 0; 161 | reader.readBytes(data, function(data) { 162 | expect(data).toEqual(output); 163 | done(); 164 | }, errFail); 165 | }); 166 | 167 | 168 | it('refuses bad framing', function(done) { 169 | reader.readBytes([12, 3, 2, 14, 8, 1, 9, 0], cbFail, 170 | function(reason) { 171 | expect(reason).toEqual('frame'); 172 | done(); 173 | }); 174 | }); 175 | 176 | it('refuses bad checksum', function(done) { 177 | reader.readBytes([7, 1, 2, 2, 2, 2, 2, 0], cbFail, 178 | function(reason) { 179 | expect(reason).toEqual('checksum'); 180 | done(); 181 | }); 182 | }); 183 | 184 | it('refuses short packets', function(done) { 185 | reader.readBytes([1, 0], cbFail, 186 | function(reason) { 187 | expect(reason).toEqual('frame'); 188 | done(); 189 | }); 190 | }); 191 | 192 | it('fails on buffer overflow', function(done) { 193 | var counter = 0; 194 | var data = new Uint8Array(2501); 195 | for (var i = 0; i < 2500; ++i) { 196 | data[i] = i % 250 ? 250 : 67; 197 | } 198 | data[2500] = 0; 199 | var short_reader = new cobs.Reader(700); 200 | short_reader.readBytes(data, cbFail, function(reason) { 201 | if (counter >= 0 && counter < 3) { 202 | expect(reason).toEqual('overflow'); 203 | } else if (counter === 3) { 204 | expect(reason).toEqual('frame'); 205 | done(); 206 | } 207 | ++counter; 208 | }); 209 | }); 210 | 211 | 212 | it('recovers from errors', function(done) { 213 | reader.readBytes([12, 3, 2, 14, 8, 1, 9, 0], cbFail, 214 | function(reason) { 215 | expect(reason).toEqual('frame'); 216 | done(); 217 | }); 218 | reader.readBytes([7, 1, 2, 2, 2, 2, 2, 0], cbFail, 219 | function(reason) { 220 | expect(reason).toEqual('checksum'); 221 | done(); 222 | }); 223 | reader.readBytes( 224 | [1, 1, 1, 1, 1, 1, 1, 0], function(data) { 225 | expect(data).toEqual(new Uint8Array([0, 0, 0, 0, 0])); 226 | done(); 227 | }, errFail); 228 | }); 229 | 230 | function cbFail() { 231 | fail('Unexpected invocation of callback'); 232 | } 233 | 234 | function errFail(reason) { 235 | fail('Unexpected invocation of error due to reason: ' + reason); 236 | } 237 | }); 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /test/commandLog.js: -------------------------------------------------------------------------------- 1 | describe('Command log service', function() { 2 | 3 | var commandLog; 4 | var $rootScope; 5 | 6 | beforeEach(angular.mock.module('flybrixCommon')); 7 | 8 | beforeEach(inject(function(_commandLog_, _$rootScope_) { 9 | commandLog = _commandLog_; 10 | $rootScope = _$rootScope_; 11 | })); 12 | 13 | it('exists', function() { 14 | expect(commandLog).toBeDefined(); 15 | }); 16 | 17 | describe('.read()', function() { 18 | it('exists', function() { 19 | expect(commandLog.read).toBeDefined(); 20 | }); 21 | 22 | it('returns list', function() { 23 | expect(commandLog.read()).toEqual([]); 24 | }); 25 | }); 26 | 27 | describe('.log()', function() { 28 | it('exists', function() { 29 | expect(commandLog.log).toBeDefined(); 30 | }); 31 | 32 | it('adds strings to list', function() { 33 | commandLog.log('Entry 1'); 34 | expect(commandLog.read()).toEqual(['Entry 1']); 35 | }); 36 | 37 | it('is equivalent to service root', function() { 38 | commandLog('Entry 1'); 39 | expect(commandLog.read()).toEqual(['Entry 1']); 40 | }); 41 | 42 | it('accummulates', function() { 43 | commandLog('Entry A'); 44 | expect(commandLog.read()).toEqual(['Entry A']); 45 | commandLog('Entry B'); 46 | expect(commandLog.read()).toEqual(['Entry A', 'Entry B']); 47 | commandLog('E. C'); 48 | expect(commandLog.read()).toEqual(['Entry A', 'Entry B', 'E. C']); 49 | }); 50 | 51 | it('ignores empty messages', function() { 52 | commandLog('Entry A'); 53 | expect(commandLog.read()).toEqual(['Entry A']); 54 | commandLog(); 55 | expect(commandLog.read()).toEqual(['Entry A']); 56 | commandLog('Entry B'); 57 | expect(commandLog.read()).toEqual(['Entry A', 'Entry B']); 58 | }); 59 | }); 60 | 61 | describe('.onMessage()', function() { 62 | it('exists', function() { 63 | expect(commandLog.onMessage).toBeDefined(); 64 | }); 65 | 66 | it('responds to a log message', function(done) { 67 | commandLog.onMessage(function(v) { 68 | expect(v).toEqual(['Entry 1']); 69 | done(); 70 | }); 71 | $rootScope.$digest(); 72 | 73 | commandLog('Entry 1'); 74 | $rootScope.$digest(); 75 | }); 76 | 77 | it('does not respond to a log message retroactively', function(done) { 78 | commandLog('Entry 1'); 79 | $rootScope.$digest(); 80 | 81 | commandLog.onMessage(function(v) { 82 | expect(v).toEqual(['Entry 1', 'Entry 2']); 83 | done(); 84 | }); 85 | $rootScope.$digest(); 86 | 87 | commandLog('Entry 2'); 88 | $rootScope.$digest(); 89 | }); 90 | 91 | it('responds to multiple messages', function(done) { 92 | var call_id = 0; 93 | commandLog.onMessage(function(v) { 94 | switch (call_id++) { 95 | case 0: 96 | expect(v).toEqual(['Entry 1']); 97 | break; 98 | case 1: 99 | expect(v).toEqual(['Entry 1', 'Entry 2']); 100 | break; 101 | case 2: 102 | expect(v).toEqual(['Entry 1', 'Entry 2', 'Entry 3']); 103 | done(); 104 | break; 105 | default: 106 | fail('Impossible path to take') 107 | } 108 | }); 109 | $rootScope.$digest(); 110 | 111 | commandLog('Entry 1'); 112 | $rootScope.$digest(); 113 | 114 | commandLog('Entry 2'); 115 | $rootScope.$digest(); 116 | 117 | commandLog('Entry 3'); 118 | $rootScope.$digest(); 119 | }); 120 | 121 | it('allows multiple callback handlers', function(done) { 122 | var called_a = false; 123 | var called_b = false; 124 | function finalizer() { 125 | if (called_a && called_b) { 126 | done(); 127 | } 128 | } 129 | 130 | commandLog.onMessage(function(v) { 131 | expect(v).toEqual(['Entry 1']); 132 | if (called_a) { 133 | fail('Multiple calls to the first callback'); 134 | } 135 | called_a = true; 136 | finalizer(); 137 | }); 138 | $rootScope.$digest(); 139 | 140 | commandLog.onMessage(function(v) { 141 | expect(v).toEqual(['Entry 1']); 142 | if (called_b) { 143 | fail('Multiple calls to the second callback'); 144 | } 145 | called_b = true; 146 | finalizer(); 147 | }); 148 | $rootScope.$digest(); 149 | 150 | commandLog('Entry 1'); 151 | $rootScope.$digest(); 152 | }); 153 | }); 154 | 155 | describe('.clearSubscribers()', function() { 156 | it('exists', function() { 157 | expect(commandLog.clearSubscribers).toBeDefined(); 158 | }); 159 | 160 | it('clears subscribers', function(done) { 161 | var called_a = false; 162 | var called_b = false; 163 | 164 | commandLog.onMessage(function(v) { 165 | if (called_a) { 166 | fail('Multiple calls to the first callback'); 167 | } 168 | expect(v).toEqual(['Entry 1']); 169 | called_a = true; 170 | }); 171 | $rootScope.$digest(); 172 | 173 | commandLog.onMessage(function(v) { 174 | if (called_b) { 175 | fail('Multiple calls to the second callback'); 176 | } 177 | expect(v).toEqual(['Entry 1']); 178 | called_b = true; 179 | }); 180 | $rootScope.$digest(); 181 | 182 | commandLog('Entry 1'); 183 | $rootScope.$digest(); 184 | 185 | commandLog.clearSubscribers(); 186 | $rootScope.$digest(); 187 | 188 | commandLog.onMessage(function(v) { 189 | expect(v).toEqual(['Entry 1', 'Entry 2']); 190 | if (called_a && called_b) { 191 | done(); 192 | } 193 | }); 194 | $rootScope.$digest(); 195 | commandLog('Entry 2'); 196 | 197 | $rootScope.$digest(); 198 | }); 199 | }); 200 | 201 | it('gives the same results in queries and callbacks', function(done) { 202 | var countdown = 5; 203 | commandLog.onMessage(function(v) { 204 | expect(v).toEqual(commandLog.read()); 205 | if (--countdown === 0) { 206 | done(); 207 | } 208 | }); 209 | $rootScope.$digest(); 210 | 211 | commandLog('Entry 1'); 212 | $rootScope.$digest(); 213 | 214 | commandLog('Entry 2'); 215 | $rootScope.$digest(); 216 | 217 | commandLog('Entry 3'); 218 | $rootScope.$digest(); 219 | 220 | commandLog('Entry 4'); 221 | $rootScope.$digest(); 222 | 223 | commandLog('Entry 5'); 224 | $rootScope.$digest(); 225 | }); 226 | 227 | }); 228 | -------------------------------------------------------------------------------- /test/deviceConfig.js: -------------------------------------------------------------------------------- 1 | describe('Device configuration service', function() { 2 | var deviceConfig; 3 | var serial; 4 | var cobs; 5 | var commandLog; 6 | var firmwareVersion; 7 | var $timeout; 8 | var $rootScope; 9 | var backend; 10 | 11 | beforeEach(angular.mock.module('flybrixCommon')); 12 | 13 | beforeEach(inject(function(_deviceConfig_, _serial_, _cobs_, _commandLog_, _firmwareVersion_, _$timeout_, _$rootScope_) { 14 | firmwareVersion = _firmwareVersion_; 15 | deviceConfig = _deviceConfig_; 16 | serial = _serial_; 17 | cobs = _cobs_; 18 | commandLog = _commandLog_; 19 | $timeout = _$timeout_; 20 | $rootScope = _$rootScope_; 21 | })); 22 | 23 | beforeEach(function() { 24 | backend = new serial.Backend(); 25 | serial.setBackend(backend); 26 | }); 27 | 28 | it('exists', function() { 29 | expect(deviceConfig).toBeDefined(); 30 | }); 31 | 32 | describe('.getDesiredVersion()', function() { 33 | it('exists', function() { 34 | expect(deviceConfig.getDesiredVersion).toBeDefined(); 35 | }); 36 | 37 | it('equals newest firmware version', function() { 38 | expect(deviceConfig.getDesiredVersion()).toEqual([1, 6, 0]); 39 | }); 40 | }); 41 | 42 | describe('.getConfig()', function() { 43 | it('exists', function() { 44 | expect(deviceConfig.getConfig).toBeDefined(); 45 | }); 46 | 47 | it('defaults to zeros', function() { 48 | expect(deviceConfig.getConfig()).toEqual(firmwareVersion.serializationHandler().Configuration.empty()); 49 | }); 50 | }); 51 | 52 | describe('.request()', function() { 53 | it('exists', function() { 54 | expect(deviceConfig.request).toBeDefined(); 55 | }); 56 | 57 | it('requests all fields EEPROM data', function(done) { 58 | backend.send = function(val) { 59 | var decoder = new cobs.Reader(); 60 | decoder.readBytes(val, function(data) { 61 | expect(data).toEqual(new Uint8Array([1, 1, 0, 64, 0, 255, 15, 255, 255])); 62 | done(); 63 | }); 64 | }; 65 | deviceConfig.request(); 66 | $timeout.flush(); 67 | }); 68 | }); 69 | 70 | describe('.reinit()', function() { 71 | it('exists', function() { 72 | expect(deviceConfig.request).toBeDefined(); 73 | }); 74 | 75 | it('reinits EEPROM data', function(done) { 76 | backend.send = function(val) { 77 | var decoder = new cobs.Reader(); 78 | decoder.readBytes(val, function(data) { 79 | expect(data).toEqual(new Uint8Array([1, 5, 0, 0, 0])); 80 | done(); 81 | }); 82 | }; 83 | deviceConfig.reinit(); 84 | $timeout.flush(); 85 | }); 86 | 87 | it('requests new EEPROM data upon confirmation', function(done) { 88 | var datas = [ 89 | [1, 5, 0, 0, 0], 90 | [1, 1, 0, 64, 0, 255, 15, 255, 255] 91 | ]; 92 | var call_case = 0; 93 | 94 | backend.send = function(val) { 95 | var decoder = new cobs.Reader(); 96 | decoder.readBytes(val, function(data) { 97 | expect(data).toEqual(new Uint8Array(datas[call_case])); 98 | ++call_case; 99 | if (call_case === 1) { 100 | backend.onRead(new Uint8Array( 101 | [4, 254, 255, 5, 1, 1, 2, 4, 1, 1, 1, 0])); 102 | } else if (call_case === 2) { 103 | done(); 104 | } 105 | }); 106 | }; 107 | deviceConfig.reinit(); 108 | $timeout.flush(); 109 | }); 110 | 111 | it('reports failure', function(done) { 112 | backend.send = function(val) { 113 | var decoder = new cobs.Reader(); 114 | decoder.readBytes(val, function(data) { 115 | expect(data).toEqual(new Uint8Array([1, 5, 0, 0, 0])); 116 | commandLog.onMessage(function(messages) { 117 | expect(messages.length).toBe(2); 118 | expect(messages[1]) 119 | .toEqual( 120 | 'Request for factory reset failed: ' + 121 | 'Request was not fully processed'); 122 | done(); 123 | }); 124 | backend.onRead(new Uint8Array( 125 | [4, 255, 255, 5, 1, 1, 2, 5, 1, 1, 1, 0])); 126 | }); 127 | }; 128 | deviceConfig.reinit(); 129 | $timeout.flush(); 130 | }); 131 | }); 132 | 133 | describe('.send()', function() { 134 | var empty_config = Array.apply(null, Array(841)).map(function() { 135 | return 0; 136 | }); 137 | empty_config[0] = 255; 138 | empty_config[1] = 15; 139 | empty_config[83] = 63; 140 | empty_config[348] = 255; 141 | empty_config[365] = empty_config[366] = 255; 142 | empty_config[816] = 255; 143 | for (var idx = 0; idx < 16; ++idx) { 144 | empty_config[367 + idx * 17] = empty_config[368 + idx * 17] = 255; 145 | } 146 | 147 | it('exists', function() { 148 | expect(deviceConfig.send).toBeDefined(); 149 | }); 150 | 151 | it('sends the present config by default', function(done) { 152 | backend.send = function(val) { 153 | var decoder = new cobs.Reader(); 154 | decoder.readBytes(val, function(data) { 155 | expect(data.subarray(0, 5)).toEqual(new Uint8Array([1, 1, 0, 16, 0])); 156 | expect(data.subarray(5)).toEqual(new Uint8Array(empty_config)); 157 | done(); 158 | }); 159 | }; 160 | deviceConfig.send(); 161 | $timeout.flush(); 162 | }); 163 | 164 | it('sends a new config when defined', function(done) { 165 | backend.send = function(val) { 166 | var decoder = new cobs.Reader(); 167 | decoder.readBytes(val, function(data) { 168 | var expected_data = new Uint8Array(empty_config.slice()); 169 | expected_data[2] = 1; 170 | expected_data[3] = 5; 171 | expect(data.subarray(0, 5)).toEqual(new Uint8Array([1, 1, 0, 16, 0])); 172 | expect(data.subarray(5)).toEqual(expected_data); 173 | done(); 174 | }); 175 | }; 176 | var config_copy = 177 | JSON.parse(JSON.stringify(deviceConfig.getConfig())); 178 | config_copy.version = { 179 | major: 1, 180 | minor: 5, 181 | patch: 0, 182 | }; 183 | deviceConfig.send(config_copy); 184 | $timeout.flush(); 185 | }); 186 | 187 | 188 | it('requests new config upon response', function(done) { 189 | var counter = 0; 190 | backend.send = function(val) { 191 | var decoder = new cobs.Reader(); 192 | decoder.readBytes(val, function(data) { 193 | if (counter === 0) { 194 | expect(data.subarray(0, 5)).toEqual(new Uint8Array([1, 1, 0, 16, 0])); 195 | expect(data.subarray(5)).toEqual(new Uint8Array(empty_config)); 196 | backend.onRead(new Uint8Array( 197 | [4, 254, 255, 1, 2, 16, 1, 1, 2, 16, 1, 0])); 198 | } else if (counter === 1) { 199 | expect(data).toEqual(new Uint8Array([ 200 | 1, 1, 0, 64, 0, 255, 15, 255, 255 201 | ])); 202 | done(); 203 | } 204 | ++counter; 205 | }); 206 | }; 207 | deviceConfig.send(); 208 | $timeout.flush(); 209 | }); 210 | }); 211 | 212 | describe('.setLoggingCallback()', function() { 213 | function recalcChecksum(v) { 214 | v[0] = 0; 215 | for (var i = 0; i < v.length; ++i) { 216 | v[0] ^= v[i]; 217 | } 218 | } 219 | function generateDataFor(v, delay) { 220 | full_message_data = 221 | new Uint8Array(Array.apply(null, Array(10)).map(function() { 222 | return 0; 223 | })); 224 | full_message_data[1] = 1; 225 | var command = (1 << 19) | (1 << 16); 226 | for (var i = 0; i < 4; ++i) { 227 | full_message_data[i + 2] = ((command >> (i * 8)) & 0xFF); 228 | } 229 | full_message_data[6] = delay & 0xFF; 230 | full_message_data[7] = (delay >> 8) & 0xFF; 231 | full_message_data[8] = v; 232 | recalcChecksum(full_message_data); 233 | return full_message_data; 234 | } 235 | 236 | it('exists', function() { 237 | expect(deviceConfig.setLoggingCallback).toBeDefined(); 238 | }); 239 | 240 | it('warns in the command log by default', function(done) { 241 | commandLog.onMessage(function name(val) { 242 | if (val.indexOf( 243 | 'No callback defined for receiving logging state!' + 244 | ' Callback arguments: (isLogging, isLocked, delay)') !== 245 | -1) { 246 | done(); 247 | } 248 | }); 249 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(0)))); 250 | $rootScope.$digest(); 251 | }); 252 | 253 | it('responds to logging data', function(done) { 254 | deviceConfig.setLoggingCallback(function() { 255 | done(); 256 | }); 257 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(0)))); 258 | $rootScope.$digest(); 259 | }); 260 | 261 | it('properly decodes no logging with no lock', function(done) { 262 | deviceConfig.setLoggingCallback(function(isLogging, isLocked) { 263 | if (!isLogging && !isLocked) { 264 | done(); 265 | } 266 | }); 267 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(0)))); 268 | $rootScope.$digest(); 269 | }); 270 | 271 | it('properly decodes logging with no lock', function(done) { 272 | deviceConfig.setLoggingCallback(function(isLogging, isLocked) { 273 | if (isLogging && !isLocked) { 274 | done(); 275 | } 276 | }); 277 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(1)))); 278 | $rootScope.$digest(); 279 | }); 280 | 281 | it('properly decodes no logging with lock', function(done) { 282 | deviceConfig.setLoggingCallback(function(isLogging, isLocked) { 283 | if (!isLogging && isLocked) { 284 | done(); 285 | } 286 | }); 287 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(2)))); 288 | $rootScope.$digest(); 289 | }); 290 | 291 | it('properly decodes logging with lock', function(done) { 292 | deviceConfig.setLoggingCallback(function(isLogging, isLocked) { 293 | if (isLogging && isLocked) { 294 | done(); 295 | } 296 | }); 297 | backend.onRead(new Uint8Array(cobs.encode(generateDataFor(3)))); 298 | $rootScope.$digest(); 299 | }); 300 | 301 | it('properly decodes delay', function(done) { 302 | deviceConfig.setLoggingCallback(function( 303 | isLogging, isLocked, delay) { 304 | if (delay === 738) { 305 | done(); 306 | } 307 | }); 308 | backend.onRead( 309 | new Uint8Array(cobs.encode(generateDataFor(0, 738)))); 310 | $rootScope.$digest(); 311 | }); 312 | }); 313 | }); 314 | -------------------------------------------------------------------------------- /test/led.js: -------------------------------------------------------------------------------- 1 | describe('LED service', function() { 2 | var led; 3 | var $rootScope; 4 | var deviceConfig; 5 | 6 | beforeEach(angular.mock.module('flybrixCommon')); 7 | 8 | beforeEach(inject(function(_led_, _deviceConfig_, _$rootScope_) { 9 | led = _led_; 10 | deviceConfig = _deviceConfig_; 11 | })); 12 | 13 | it('exists', function() { 14 | expect(led).toBeDefined(); 15 | }); 16 | 17 | describe('.set()', function() { 18 | it('exists', function() { 19 | expect(led.set).toBeDefined(); 20 | }); 21 | 22 | it('sends a partial temporary configuration', function(done) { 23 | deviceConfig.sendConfig = function(props) { 24 | var config = props.config; 25 | var temporary = props.temporary; 26 | expect(temporary).toBe(true); 27 | expect(config).toEqual({ 28 | led_states: [{ 29 | status: { 30 | _0: true, 31 | _1: true, 32 | _2: true, 33 | no_signal: true, 34 | idle: true, 35 | arming: true, 36 | recording_sd: true, 37 | _7: true, 38 | loop_slow: true, 39 | _9: true, 40 | armed: true, 41 | battery_low: true, 42 | battery_critical: true, 43 | log_full: true, 44 | crash_detected: true, 45 | override: true, 46 | }, 47 | pattern: 2, 48 | indicator_red: true, 49 | indicator_green: false, 50 | colors: { 51 | right_front: { 52 | red: 11, 53 | green: 12, 54 | blue: 13, 55 | }, 56 | right_back: { 57 | red: 21, 58 | green: 22, 59 | blue: 23, 60 | }, 61 | left_front: { 62 | red: 31, 63 | green: 32, 64 | blue: 33, 65 | }, 66 | left_back: { 67 | red: 41, 68 | green: 42, 69 | blue: 43, 70 | }, 71 | } 72 | }], 73 | }); 74 | done(); 75 | }; 76 | 77 | led.set( 78 | {red: 11, green: 12, blue: 13}, {red: 21, green: 22, blue: 23}, 79 | {red: 31, green: 32, blue: 33}, {red: 41, green: 42, blue: 43}, 80 | 2, true, false); 81 | }); 82 | 83 | it('sends default value for unset arguments', function(done) { 84 | deviceConfig.sendConfig = function(props) { 85 | var config = props.config; 86 | var temporary = props.temporary; 87 | expect(temporary).toBe(true); 88 | expect(config).toEqual({ 89 | led_states: [{ 90 | status: { 91 | _0: true, 92 | _1: true, 93 | _2: true, 94 | no_signal: true, 95 | idle: true, 96 | arming: true, 97 | recording_sd: true, 98 | _7: true, 99 | loop_slow: true, 100 | _9: true, 101 | armed: true, 102 | battery_low: true, 103 | battery_critical: true, 104 | log_full: true, 105 | crash_detected: true, 106 | override: true, 107 | }, 108 | pattern: 5, 109 | indicator_red: false, 110 | indicator_green: false, 111 | colors: { 112 | right_front: { 113 | red: 0, 114 | green: 0, 115 | blue: 0, 116 | }, 117 | right_back: { 118 | red: 0, 119 | green: 0, 120 | blue: 0, 121 | }, 122 | left_front: { 123 | red: 0, 124 | green: 0, 125 | blue: 0, 126 | }, 127 | left_back: { 128 | red: 0, 129 | green: 0, 130 | blue: 0, 131 | }, 132 | } 133 | }], 134 | }); 135 | done(); 136 | }; 137 | 138 | led.set(); 139 | }); 140 | 141 | it('sends previous value for unset arguments', function(done) { 142 | var counter = 4; 143 | deviceConfig.sendConfig = function(props) { 144 | var config = props.config; 145 | var temporary = props.temporary; 146 | expect(temporary).toBe(true); 147 | expect(config).toEqual({ 148 | led_states: [{ 149 | status: { 150 | _0: true, 151 | _1: true, 152 | _2: true, 153 | no_signal: true, 154 | idle: true, 155 | arming: true, 156 | recording_sd: true, 157 | _7: true, 158 | loop_slow: true, 159 | _9: true, 160 | armed: true, 161 | battery_low: true, 162 | battery_critical: true, 163 | log_full: true, 164 | crash_detected: true, 165 | override: true, 166 | }, 167 | pattern: 2, 168 | indicator_red: true, 169 | indicator_green: false, 170 | colors: { 171 | right_front: { 172 | red: 11, 173 | green: 12, 174 | blue: 13, 175 | }, 176 | right_back: { 177 | red: 21, 178 | green: 22, 179 | blue: 23, 180 | }, 181 | left_front: { 182 | red: 31, 183 | green: 32, 184 | blue: 33, 185 | }, 186 | left_back: { 187 | red: 41, 188 | green: 42, 189 | blue: 43, 190 | }, 191 | } 192 | }], 193 | }); 194 | if (!--counter) { 195 | done(); 196 | } 197 | }; 198 | 199 | led.set( 200 | {red: 11, green: 12, blue: 13}, {red: 21, green: 22, blue: 23}, 201 | {red: 31, green: 32, blue: 33}, {red: 41, green: 42, blue: 43}, 202 | 2, true, false); 203 | 204 | led.set(); 205 | led.set(); 206 | led.set(); 207 | }); 208 | }); 209 | 210 | describe('.setSimple()', function() { 211 | it('exists', function() { 212 | expect(led.setSimple).toBeDefined(); 213 | }); 214 | 215 | it('sends a partial temporary configuration', function(done) { 216 | deviceConfig.sendConfig = function(props) { 217 | var config = props.config; 218 | var temporary = props.temporary; 219 | expect(temporary).toBe(true); 220 | expect(config).toEqual({ 221 | led_states: [{ 222 | status: { 223 | _0: true, 224 | _1: true, 225 | _2: true, 226 | no_signal: true, 227 | idle: true, 228 | arming: true, 229 | recording_sd: true, 230 | _7: true, 231 | loop_slow: true, 232 | _9: true, 233 | armed: true, 234 | battery_low: true, 235 | battery_critical: true, 236 | log_full: true, 237 | crash_detected: true, 238 | override: true, 239 | }, 240 | pattern: 5, 241 | indicator_red: false, 242 | indicator_green: false, 243 | colors: { 244 | right_front: { 245 | red: 11, 246 | green: 12, 247 | blue: 13, 248 | }, 249 | right_back: { 250 | red: 11, 251 | green: 12, 252 | blue: 13, 253 | }, 254 | left_front: { 255 | red: 11, 256 | green: 12, 257 | blue: 13, 258 | }, 259 | left_back: { 260 | red: 11, 261 | green: 12, 262 | blue: 13, 263 | }, 264 | } 265 | }], 266 | }); 267 | done(); 268 | }; 269 | 270 | led.setSimple(11, 12, 13); 271 | }); 272 | 273 | it('sends zeros by default', function(done) { 274 | var counter = 1; 275 | deviceConfig.sendConfig = function(props) { 276 | var config = props.config; 277 | var temporary = props.temporary; 278 | expect(temporary).toBe(true); 279 | expect(config).toEqual({ 280 | led_states: [{ 281 | status: { 282 | _0: true, 283 | _1: true, 284 | _2: true, 285 | no_signal: true, 286 | idle: true, 287 | arming: true, 288 | recording_sd: true, 289 | _7: true, 290 | loop_slow: true, 291 | _9: true, 292 | armed: true, 293 | battery_low: true, 294 | battery_critical: true, 295 | log_full: true, 296 | crash_detected: true, 297 | override: true, 298 | }, 299 | pattern: 5, 300 | indicator_red: false, 301 | indicator_green: false, 302 | colors: { 303 | right_front: { 304 | red: counter * 11, 305 | green: counter * 12, 306 | blue: counter * 13, 307 | }, 308 | right_back: { 309 | red: counter * 11, 310 | green: counter * 12, 311 | blue: counter * 13, 312 | }, 313 | left_front: { 314 | red: counter * 11, 315 | green: counter * 12, 316 | blue: counter * 13, 317 | }, 318 | left_back: { 319 | red: counter * 11, 320 | green: counter * 12, 321 | blue: counter * 13, 322 | }, 323 | } 324 | }], 325 | }); 326 | if (!counter--) { 327 | done(); 328 | } 329 | }; 330 | 331 | led.setSimple(11, 12, 13); 332 | led.setSimple(); 333 | }); 334 | }); 335 | }); 336 | -------------------------------------------------------------------------------- /test/rcData.js: -------------------------------------------------------------------------------- 1 | describe('RC Data service', function() { 2 | var rcData; 3 | var serial; 4 | var cobs; 5 | var $timeout; 6 | 7 | beforeEach(angular.mock.module('flybrixCommon')); 8 | 9 | beforeEach( 10 | inject(function(_rcData_, _serial_, _cobs_, _$timeout_) { 11 | rcData = _rcData_; 12 | serial = _serial_; 13 | cobs = _cobs_; 14 | $timeout = _$timeout_; 15 | })); 16 | 17 | it('exists', function() { 18 | expect(rcData).toBeDefined(); 19 | }); 20 | 21 | describe('AUX', function() { 22 | it('exists', function() { 23 | expect(rcData.AUX).toBeDefined(); 24 | }); 25 | 26 | it('matches flag indices', function() { 27 | expect(rcData.AUX.LOW).toBe(0); 28 | expect(rcData.AUX.MID).toBe(1); 29 | expect(rcData.AUX.HIGH).toBe(2); 30 | }); 31 | 32 | }); 33 | 34 | describe('.AUX1', function() { 35 | it('has setter', function() { 36 | expect(rcData.setAux1).toBeDefined(); 37 | }); 38 | 39 | it('has getter', function() { 40 | expect(rcData.getAux1).toBeDefined(); 41 | }); 42 | 43 | it('defaults to HIGH', function() { 44 | expect(rcData.getAux1()).toBe(rcData.AUX.HIGH); 45 | }); 46 | 47 | it('is stored', function() { 48 | rcData.setAux1(rcData.AUX.MID); 49 | expect(rcData.getAux1()).toBe(rcData.AUX.MID); 50 | rcData.setAux1(rcData.AUX.LOW); 51 | expect(rcData.getAux1()).toBe(rcData.AUX.LOW); 52 | rcData.setAux1(rcData.AUX.HIGH); 53 | expect(rcData.getAux1()).toBe(rcData.AUX.HIGH); 54 | }); 55 | }); 56 | 57 | describe('AUX2', function() { 58 | it('has setter', function() { 59 | expect(rcData.setAux2).toBeDefined(); 60 | }); 61 | 62 | it('has getter', function() { 63 | expect(rcData.getAux2).toBeDefined(); 64 | }); 65 | 66 | it('defaults to HIGH', function() { 67 | expect(rcData.getAux2()).toBe(rcData.AUX.HIGH); 68 | }); 69 | 70 | it('is stored', function() { 71 | rcData.setAux2(rcData.AUX.MID); 72 | expect(rcData.getAux2()).toBe(rcData.AUX.MID); 73 | rcData.setAux2(rcData.AUX.LOW); 74 | expect(rcData.getAux2()).toBe(rcData.AUX.LOW); 75 | rcData.setAux2(rcData.AUX.HIGH); 76 | expect(rcData.getAux2()).toBe(rcData.AUX.HIGH); 77 | }); 78 | }); 79 | 80 | describe('throttle axis', function() { 81 | it('has setter', function() { 82 | expect(rcData.setThrottle).toBeDefined(); 83 | }); 84 | 85 | it('has getter', function() { 86 | expect(rcData.getThrottle).toBeDefined(); 87 | }); 88 | 89 | it('defaults to -1', function() { 90 | expect(rcData.getThrottle()).toBe(-1); 91 | }); 92 | 93 | it('is stored', function() { 94 | rcData.setThrottle(0.4); 95 | expect(rcData.getThrottle()).toBe(0.4); 96 | rcData.setThrottle(0.2); 97 | expect(rcData.getThrottle()).toBe(0.2); 98 | rcData.setThrottle(-0.7); 99 | expect(rcData.getThrottle()).toBe(-0.7); 100 | }); 101 | }); 102 | 103 | describe('pitch axis', function() { 104 | it('has setter', function() { 105 | expect(rcData.setPitch).toBeDefined(); 106 | }); 107 | 108 | it('has getter', function() { 109 | expect(rcData.getPitch).toBeDefined(); 110 | }); 111 | 112 | it('defaults to 0', function() { 113 | expect(rcData.getPitch()).toBe(0); 114 | }); 115 | 116 | it('is stored', function() { 117 | rcData.setPitch(0.4); 118 | expect(rcData.getPitch()).toBe(0.4); 119 | rcData.setPitch(0.2); 120 | expect(rcData.getPitch()).toBe(0.2); 121 | rcData.setPitch(-0.7); 122 | expect(rcData.getPitch()).toBe(-0.7); 123 | }); 124 | }); 125 | 126 | describe('roll axis', function() { 127 | it('has setter', function() { 128 | expect(rcData.setRoll).toBeDefined(); 129 | }); 130 | 131 | it('has getter', function() { 132 | expect(rcData.getRoll).toBeDefined(); 133 | }); 134 | 135 | it('defaults to 0', function() { 136 | expect(rcData.getRoll()).toBe(0); 137 | }); 138 | 139 | it('is stored', function() { 140 | rcData.setRoll(0.4); 141 | expect(rcData.getRoll()).toBe(0.4); 142 | rcData.setRoll(0.2); 143 | expect(rcData.getRoll()).toBe(0.2); 144 | rcData.setRoll(-0.7); 145 | expect(rcData.getRoll()).toBe(-0.7); 146 | }); 147 | }); 148 | 149 | describe('yaw axis', function() { 150 | it('has setter', function() { 151 | expect(rcData.setYaw).toBeDefined(); 152 | }); 153 | 154 | it('has getter', function() { 155 | expect(rcData.getYaw).toBeDefined(); 156 | }); 157 | 158 | it('defaults to 0', function() { 159 | expect(rcData.getYaw()).toBe(0); 160 | }); 161 | 162 | it('is stored', function() { 163 | rcData.setYaw(0.4); 164 | expect(rcData.getYaw()).toBe(0.4); 165 | rcData.setYaw(0.2); 166 | expect(rcData.getYaw()).toBe(0.2); 167 | rcData.setYaw(-0.7); 168 | expect(rcData.getYaw()).toBe(-0.7); 169 | }); 170 | }); 171 | 172 | describe('.send()', function() { 173 | var backend; 174 | 175 | beforeEach(function() { 176 | backend = new serial.Backend(); 177 | serial.setBackend(backend); 178 | }); 179 | 180 | it('exists', function() { 181 | expect(rcData.send).toBeDefined(); 182 | }); 183 | 184 | it('sends serial messages', function(done) { 185 | backend.send = function() { 186 | done(); 187 | }; 188 | rcData.send(); 189 | $timeout.flush(); 190 | }); 191 | 192 | it('sends same data as radio RC by default', function(done) { 193 | backend.send = function(val) { 194 | var decoder = new cobs.Reader(); 195 | decoder.readBytes(val, function(data) { 196 | expect(data).toEqual( 197 | new Uint8Array([1, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 36])); 198 | done(); 199 | }, fail); 200 | }; 201 | rcData.send(); 202 | $timeout.flush(); 203 | }); 204 | 205 | it('responds to AUX changes', function(done) { 206 | backend.send = function(val) { 207 | var decoder = new cobs.Reader(); 208 | decoder.readBytes(val, function(data) { 209 | expect(data).toEqual( 210 | new Uint8Array([1, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 17])); 211 | done(); 212 | }); 213 | }; 214 | rcData.setAux1(rcData.AUX.LOW); 215 | rcData.setAux2(rcData.AUX.MID); 216 | rcData.send(); 217 | $timeout.flush(); 218 | }); 219 | 220 | it('responds to movement of axes', function(done) { 221 | backend.send = function(val) { 222 | var decoder = new cobs.Reader(); 223 | decoder.readBytes(val, function(data) { 224 | expect(data).not.toEqual( 225 | new Uint8Array([1, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 36])); 226 | done(); 227 | }); 228 | }; 229 | rcData.setYaw(1); 230 | rcData.setPitch(-1); 231 | rcData.send(); 232 | $timeout.flush(); 233 | }); 234 | 235 | it('is urgent by default', function() { 236 | var leftover = 3; 237 | backend.send = function(val) { 238 | expect(val.length).toBe(18); 239 | expect(val[16]).toBe(36); 240 | --leftover; 241 | }; 242 | backend.busy = alwaysTrue; 243 | rcData.setAux1(2); 244 | rcData.setAux2(2); 245 | rcData.send(); 246 | rcData.setAux1(0); 247 | rcData.setAux2(0); 248 | rcData.send(); 249 | rcData.send(); 250 | rcData.send(); 251 | backend.busy = alwaysFalse; 252 | rcData.setAux1(2); 253 | rcData.setAux2(2); 254 | rcData.send(); 255 | rcData.send(); 256 | $timeout.flush(); 257 | expect(leftover).toBe(0); 258 | }); 259 | 260 | it('can be forced to send', function() { 261 | var leftover = 3; 262 | backend.send = function(val) { 263 | expect(val.length).toBe(18); 264 | expect(val[16]).toBe(36); 265 | --leftover; 266 | }; 267 | backend.busy = alwaysTrue; 268 | rcData.setAux1(2); 269 | rcData.setAux2(2); 270 | rcData.send(); 271 | rcData.setAux1(0); 272 | rcData.setAux2(0); 273 | rcData.send(); 274 | rcData.send(); 275 | rcData.send(); 276 | rcData.setAux1(2); 277 | rcData.setAux2(2); 278 | rcData.forceNextSend(); 279 | rcData.send(); 280 | rcData.setAux1(0); 281 | rcData.setAux2(0); 282 | rcData.setAux1(2); 283 | rcData.setAux2(2); 284 | rcData.forceNextSend(); 285 | rcData.send(); 286 | $timeout.flush(); 287 | expect(leftover).toBe(0); 288 | }); 289 | 290 | function alwaysTrue() { 291 | return true; 292 | } 293 | 294 | function alwaysFalse() { 295 | return false; 296 | } 297 | }); 298 | }); 299 | -------------------------------------------------------------------------------- /test/serial.js: -------------------------------------------------------------------------------- 1 | describe('Serial service', function() { 2 | var serial; 3 | var commandLog; 4 | var $rootScope; 5 | var $timeout; 6 | 7 | beforeEach(angular.mock.module('flybrixCommon')); 8 | 9 | beforeEach( 10 | inject(function(_serial_, _commandLog_, _$rootScope_, _$timeout_) { 11 | serial = _serial_; 12 | commandLog = _commandLog_; 13 | $rootScope = _$rootScope_; 14 | $timeout = _$timeout_; 15 | })); 16 | 17 | it('exists', function() { 18 | expect(serial).toBeDefined(); 19 | }); 20 | 21 | describe('.Backend()', function() { 22 | var backend; 23 | 24 | beforeEach(function() { 25 | backend = new serial.Backend(); 26 | }); 27 | 28 | it('exists', function() { 29 | expect(serial.Backend).toBeDefined(); 30 | }); 31 | 32 | it('is constructor', function() { 33 | expect(new serial.Backend()).toBeDefined(); 34 | }); 35 | 36 | describe('.busy()', function() { 37 | it('exists', function() { 38 | expect(backend.busy).toBeDefined(); 39 | }); 40 | 41 | it('is false by default', function() { 42 | expect(backend.busy()).toBeFalsy(); 43 | }); 44 | }); 45 | 46 | describe('.onRead()', function() { 47 | it('exists', function() { 48 | expect(backend.onRead).toBeDefined(); 49 | }); 50 | 51 | it('logs issues by default', function(done) { 52 | commandLog.onMessage(function(msgs) { 53 | expect(msgs.length).toBe(1); 54 | expect(msgs[0]).toEqual( 55 | 'No "onRead" defined for serial backend'); 56 | done(); 57 | }); 58 | backend.onRead(); 59 | $rootScope.$digest(); 60 | }); 61 | }); 62 | 63 | describe('.send()', function() { 64 | it('exists', function() { 65 | expect(backend.send).toBeDefined(); 66 | }); 67 | 68 | it('logs issues by default', function(done) { 69 | commandLog.onMessage(function(msgs) { 70 | expect(msgs.length).toBe(1); 71 | expect(msgs[0]).toEqual( 72 | 'No "send" defined for serial backend'); 73 | done(); 74 | }); 75 | backend.send(); 76 | $rootScope.$digest(); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('.setBackend()', function() { 82 | it('exists', function() { 83 | expect(serial.setBackend).toBeDefined(); 84 | }); 85 | 86 | it('sets the serial backend', function(done) { 87 | var backend = new serial.Backend(); 88 | backend.busy = function() { 89 | done(); 90 | return true; 91 | }; 92 | serial.setBackend(backend); 93 | serial.busy(); 94 | }); 95 | 96 | describe('.onRead()', function() { 97 | var backend; 98 | 99 | beforeEach(function() { 100 | backend = new serial.Backend(); 101 | serial.setBackend(backend); 102 | }); 103 | 104 | it('exists', function() { 105 | expect(backend.onRead).toBeDefined(); 106 | }); 107 | 108 | it('causes serial to read', function(done) { 109 | serial.setBytesHandler(function(data) { 110 | expect(data).toEqual(new Uint8Array([1, 2, 3, 4])); 111 | done(); 112 | }); 113 | backend.onRead(new Uint8Array([1, 2, 3, 4])) 114 | }); 115 | }); 116 | }); 117 | 118 | describe('.busy()', function() { 119 | it('exists', function() { 120 | expect(serial.busy).toBeDefined(); 121 | }); 122 | 123 | it('is false by default', function() { 124 | expect(serial.busy()).toBeFalsy(); 125 | }); 126 | 127 | it('uses the backend to make answers', function() { 128 | var backend = new serial.Backend(); 129 | backend.is_busy = false; 130 | backend.busy = function() { 131 | return this.is_busy; 132 | }; 133 | 134 | serial.setBackend(backend); 135 | 136 | expect(serial.busy()).toBeFalsy(); 137 | 138 | backend.is_busy = true; 139 | expect(serial.busy()).toBeTruthy(); 140 | 141 | backend.is_busy = false; 142 | expect(serial.busy()).toBeFalsy(); 143 | }); 144 | }); 145 | 146 | describe('.setBytesHandler()', function() { 147 | it('exists', function() { 148 | expect(serial.setBytesHandler).toBeDefined(); 149 | }); 150 | 151 | it('sets the response to reading', function(done) { 152 | var backend = new serial.Backend(); 153 | serial.setBackend(backend); 154 | serial.setBytesHandler(function(data) { 155 | expect(data).toEqual(new Uint8Array([1, 2, 3, 4])); 156 | done(); 157 | }); 158 | backend.onRead(new Uint8Array([1, 2, 3, 4])); 159 | }); 160 | }); 161 | 162 | describe('reading data', function() { 163 | var backend; 164 | 165 | beforeEach(function() { 166 | backend = new serial.Backend(); 167 | serial.setBackend(backend); 168 | }); 169 | 170 | it('reads states', function(done) { 171 | serial.addOnReceiveCallback(function(messageType, message) { 172 | if (messageType !== 'State') { 173 | fail('Unexpected message type ' + messageType); 174 | } 175 | expect(message).toEqual({ 176 | timestamp_us: 0x04030201, 177 | loop_count: 0x08070605, 178 | serial_update_rate_estimate: NaN, 179 | }); 180 | done(); 181 | }); 182 | commandLog.onMessage(onFail); 183 | backend.onRead(new Uint8Array( 184 | [2, 13, 2, 1, 1, 10, 4, 1, 2, 3, 4, 5, 6, 7, 8, 0])); 185 | $rootScope.$digest(); 186 | }); 187 | 188 | it('reads commands', function(done) { 189 | serial.addOnReceiveCallback(function(messageType, message) { 190 | if (messageType !== 'Command') { 191 | fail('Unexpected message type ' + messageType); 192 | } 193 | expect(message).toEqual({ 194 | request_response: true, 195 | motor_override_speed_4: 0x0201, 196 | set_sd_write_delay: 0x0403, 197 | set_led: { 198 | pattern: 1, 199 | color_right: { 200 | red: 2, 201 | green: 3, 202 | blue: 4, 203 | }, 204 | color_left: { 205 | red: 1, 206 | green: 2, 207 | blue: 3, 208 | }, 209 | indicator_red: true, 210 | indicator_green: true, 211 | }, 212 | set_calibration: { 213 | enabled: false, 214 | mode: 5, 215 | }, 216 | }); 217 | done(); 218 | }); 219 | commandLog.onMessage(onFail); 220 | backend.onRead( 221 | new Uint8Array([20, 1, 1, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 5, 0])); 222 | $rootScope.$digest(); 223 | }); 224 | 225 | it('reports bad data', function(done) { 226 | serial.addOnReceiveCallback(function(messageType) { 227 | fail('Unexpected message type ' + messageType); 228 | }); 229 | commandLog.onMessage(done); 230 | backend.onRead( 231 | new Uint8Array([11, 2, 1, 1, 2, 3, 4, 1, 2, 3, 4, 0])); 232 | $rootScope.$digest(); 233 | }); 234 | }); 235 | 236 | describe('.handlePostConnect()', function() { 237 | var backend; 238 | beforeEach(function() { 239 | backend = new serial.Backend(); 240 | serial.setBackend(backend); 241 | }); 242 | 243 | it('exists', function() { 244 | expect(serial.handlePostConnect).toBeDefined(); 245 | }); 246 | 247 | it('sends firmware version request', function(done) { 248 | backend.send = function(data) { 249 | expect(data).toEqual( 250 | new Uint8Array([4, 65, 1, 1, 2, 64, 2, 1, 1, 0])); 251 | done(); 252 | }; 253 | serial.handlePostConnect(); 254 | $timeout.flush(); 255 | }); 256 | }); 257 | 258 | // TODO: rewrite ACK tests 259 | /* 260 | describe('acknowledging', function() { 261 | var backend; 262 | beforeEach(function() { 263 | backend = new serial.Backend(); 264 | serial.setBackend(backend); 265 | }); 266 | 267 | it('responds positively on match', function(done) { 268 | serial.send(0x87654321, []).then(done, onFail); 269 | backend.onRead(new Uint8Array([ 270 | 11, 254, 255, 0x21, 0x43, 0x65, 0x87, 0x20, 0x43, 0x65, 0x87, 0 271 | ])); 272 | $timeout.flush(); 273 | $rootScope.$digest(); 274 | }); 275 | 276 | it('responds negatively on mask mismatch', function(done) { 277 | serial.send(0x87654331, []).then(onFail, function(message) { 278 | expect(message).toEqual('Missing ACK'); 279 | done(); 280 | }); 281 | backend.onRead(new Uint8Array([ 282 | 11, 254, 255, 0x21, 0x43, 0x65, 0x87, 0x20, 0x43, 0x65, 0x87, 0 283 | ])); 284 | $timeout.flush(); 285 | $rootScope.$digest(); 286 | }); 287 | 288 | it('responds negatively on unprocessed commands', function(done) { 289 | serial.send(0x87654321, []).then(onFail, function(message) { 290 | expect(message).toEqual('Request was not fully processed'); 291 | done(); 292 | }); 293 | backend.onRead(new Uint8Array([ 294 | 11, 126, 255, 0x21, 0x43, 0x65, 0x87, 0x20, 0x43, 0x65, 0x07, 0 295 | ])); 296 | $timeout.flush(); 297 | $rootScope.$digest(); 298 | }); 299 | 300 | it('responds positively on multiple matches', function(done) { 301 | var leftover = 5; 302 | function onDone() { 303 | if (!--leftover) { 304 | done(); 305 | } 306 | } 307 | serial.send(0x87654321, []).then(onDone, onFail); 308 | serial.send(0x67854321, []).then(onDone, onFail); 309 | serial.send(0x83654721, []).then(onDone, onFail); 310 | serial.send(0x87652341, []).then(onDone, onFail); 311 | serial.send(0x81654327, []).then(onDone, onFail); 312 | backend.onRead(new Uint8Array([ 313 | 11, 254, 255, 0x21, 0x43, 0x65, 0x87, 0x20, 0x43, 0x65, 0x87, 0 314 | ])); 315 | backend.onRead(new Uint8Array([ 316 | 11, 254, 255, 0x21, 0x43, 0x85, 0x67, 0x20, 0x43, 0x85, 0x67, 0 317 | ])); 318 | backend.onRead(new Uint8Array([ 319 | 11, 254, 255, 0x21, 0x47, 0x65, 0x83, 0x20, 0x47, 0x65, 0x83, 0 320 | ])); 321 | backend.onRead(new Uint8Array([ 322 | 11, 254, 255, 0x41, 0x23, 0x65, 0x87, 0x40, 0x23, 0x65, 0x87, 0 323 | ])); 324 | backend.onRead(new Uint8Array([ 325 | 11, 254, 255, 0x27, 0x43, 0x65, 0x81, 0x26, 0x43, 0x65, 0x81, 0 326 | ])); 327 | $timeout.flush(); 328 | $rootScope.$digest(); 329 | }); 330 | 331 | it('matches responses to fitting messages', function(done) { 332 | var leftover = 2; 333 | function onDone() { 334 | if (!--leftover) { 335 | done(); 336 | } 337 | } 338 | serial.send(0x27654321, []).then(onFail, onDone); 339 | serial.send(0x87654321, []).then(onDone, onFail); 340 | backend.onRead(new Uint8Array([ 341 | 11, 254, 255, 0x21, 0x43, 0x65, 0x87, 0x20, 0x43, 0x65, 0x87, 0 342 | ])); 343 | $timeout.flush(); 344 | $rootScope.$digest(); 345 | }); 346 | }); 347 | */ 348 | 349 | function onFail(msg) { 350 | if (msg) { 351 | fail('Unexpected callback call with: ' + msg); 352 | } 353 | fail('Unexpected callback call'); 354 | } 355 | }); 356 | -------------------------------------------------------------------------------- /test/serializationHandler.js: -------------------------------------------------------------------------------- 1 | describe('Serialization handler service', function () { 2 | var serializationHandler; 3 | 4 | beforeEach(angular.mock.module('flybrixCommon')); 5 | 6 | beforeEach(inject(function (_serializationHandler_) { 7 | serializationHandler = _serializationHandler_; 8 | })); 9 | 10 | it('exists', function () { 11 | expect(serializationHandler).toBeDefined(); 12 | }); 13 | 14 | it('treats 1.6.0 as newest firmware', function () { 15 | expect(serializationHandler.getNewestVersion()).toEqual({ 16 | major: 1, 17 | minor: 6, 18 | patch: 0, 19 | }); 20 | }); 21 | 22 | describe('1.4.0', function () { 23 | var handlers = null; 24 | beforeEach(function () { 25 | handlers = serializationHandler.getHandler('1.4.0'); 26 | }); 27 | 28 | it('exists', function () { 29 | expect(handlers).toBeDefined(); 30 | }); 31 | 32 | it('has a config', function () { 33 | expect(handlers.Configuration).toBeDefined(); 34 | }); 35 | 36 | it('vector defaults to zeros', function () { 37 | expect(handlers.Vector3f.empty()).toEqual({ 38 | x: 0, 39 | y: 0, 40 | z: 0, 41 | }); 42 | }); 43 | 44 | it('color defaults to zeros', function () { 45 | expect(handlers.Color.empty()).toEqual({ 46 | red: 0, 47 | green: 0, 48 | blue: 0, 49 | }); 50 | }); 51 | 52 | it('status flag defaults to full flag', function () { 53 | expect(handlers.StatusFlag.empty()).toEqual({ 54 | boot: true, 55 | mpu_fail: true, 56 | bmp_fail: true, 57 | rx_fail: true, 58 | idle: true, 59 | enabling: true, 60 | clear_mpu_bias: true, 61 | set_mpu_bias: true, 62 | fail_stability: true, 63 | fail_angle: true, 64 | enabled: true, 65 | battery_low: true, 66 | temp_warning: true, 67 | log_full: true, 68 | fail_other: true, 69 | override: true, 70 | }); 71 | }); 72 | 73 | it('LED state case defaults to zeros', function () { 74 | expect(handlers.LEDStateCase.empty()).toEqual({ 75 | status: handlers.StatusFlag.empty(), 76 | pattern: 0, 77 | colors: { 78 | right_front: handlers.Color.empty(), 79 | right_back: handlers.Color.empty(), 80 | left_front: handlers.Color.empty(), 81 | left_back: handlers.Color.empty(), 82 | }, 83 | indicator_red: false, 84 | indicator_green: false, 85 | }); 86 | }); 87 | 88 | it('PID settings default to zero', function () { 89 | expect(handlers.PIDSettings.empty()).toEqual({ 90 | kp: 0, 91 | ki: 0, 92 | kd: 0, 93 | integral_windup_guard: 0, 94 | d_filter_time: 0, 95 | setpoint_filter_time: 0, 96 | command_to_value: 0, 97 | }); 98 | }); 99 | 100 | it('configuration defaults to zeros', function () { 101 | var zeroVector = handlers.Vector3f.empty(); 102 | var zeroLedStateCase = handlers.LEDStateCase.empty(); 103 | var zeroPidSettings = handlers.PIDSettings.empty(); 104 | var config = handlers.Configuration.empty(); 105 | expect(config).toEqual({ 106 | version: { 107 | major: 0, 108 | minor: 0, 109 | patch: 0, 110 | }, 111 | id: 0, 112 | pcb_transform: { 113 | orientation: zeroVector, 114 | translation: zeroVector, 115 | }, 116 | mix_table: { 117 | fz: [0, 0, 0, 0, 0, 0, 0, 0], 118 | tx: [0, 0, 0, 0, 0, 0, 0, 0], 119 | ty: [0, 0, 0, 0, 0, 0, 0, 0], 120 | tz: [0, 0, 0, 0, 0, 0, 0, 0], 121 | }, 122 | mag_bias: { 123 | offset: zeroVector, 124 | }, 125 | channel: { 126 | assignment: {thrust: 0, pitch: 0, roll: 0, yaw: 0, aux1: 0, aux2: 0}, 127 | inversion: {thrust: true, pitch: true, roll: true, yaw: true, aux1: true, aux2: true}, 128 | midpoint: [0, 0, 0, 0, 0, 0], 129 | deadzone: [0, 0, 0, 0, 0, 0], 130 | }, 131 | pid_parameters: { 132 | thrust_master: zeroPidSettings, 133 | pitch_master: zeroPidSettings, 134 | roll_master: zeroPidSettings, 135 | yaw_master: zeroPidSettings, 136 | thrust_slave: zeroPidSettings, 137 | pitch_slave: zeroPidSettings, 138 | roll_slave: zeroPidSettings, 139 | yaw_slave: zeroPidSettings, 140 | pid_bypass: { 141 | thrust_master: true, 142 | pitch_master: true, 143 | roll_master: true, 144 | yaw_master: true, 145 | thrust_slave: true, 146 | pitch_slave: true, 147 | roll_slave: true, 148 | yaw_slave: true, 149 | }, 150 | }, 151 | state_parameters: { 152 | state_estimation: [0, 0], 153 | enable: [0, 0], 154 | }, 155 | led_states: [ 156 | zeroLedStateCase, 157 | zeroLedStateCase, 158 | zeroLedStateCase, 159 | zeroLedStateCase, 160 | zeroLedStateCase, 161 | zeroLedStateCase, 162 | zeroLedStateCase, 163 | zeroLedStateCase, 164 | zeroLedStateCase, 165 | zeroLedStateCase, 166 | zeroLedStateCase, 167 | zeroLedStateCase, 168 | zeroLedStateCase, 169 | zeroLedStateCase, 170 | zeroLedStateCase, 171 | zeroLedStateCase, 172 | ], 173 | name: '', 174 | }); 175 | expect(config).toEqual(handlers.ConfigurationFixed.empty()); 176 | }); 177 | }); 178 | 179 | describe('1.5.0', function () { 180 | var handlers = null; 181 | beforeEach(function () { 182 | handlers = serializationHandler.getHandler('1.5.0'); 183 | }); 184 | 185 | it('exists', function () { 186 | expect(handlers).toBeDefined(); 187 | }); 188 | 189 | it('has a config', function () { 190 | expect(handlers.Configuration).toBeDefined(); 191 | }); 192 | 193 | it('vector defaults to zeros', function () { 194 | expect(handlers.Vector3f.empty()).toEqual({ 195 | x: 0, 196 | y: 0, 197 | z: 0, 198 | }); 199 | }); 200 | 201 | it('color defaults to zeros', function () { 202 | expect(handlers.Color.empty()).toEqual({ 203 | red: 0, 204 | green: 0, 205 | blue: 0, 206 | }); 207 | }); 208 | 209 | it('status flag defaults to full flag', function () { 210 | expect(handlers.StatusFlag.empty()).toEqual({ 211 | boot: true, 212 | mpu_fail: true, 213 | bmp_fail: true, 214 | rx_fail: true, 215 | idle: true, 216 | enabling: true, 217 | clear_mpu_bias: true, 218 | set_mpu_bias: true, 219 | fail_stability: true, 220 | fail_angle: true, 221 | enabled: true, 222 | battery_low: true, 223 | temp_warning: true, 224 | log_full: true, 225 | fail_other: true, 226 | override: true, 227 | }); 228 | }); 229 | 230 | it('LED state case defaults to zeros', function () { 231 | expect(handlers.LEDStateCase.empty()).toEqual({ 232 | status: handlers.StatusFlag.empty(), 233 | pattern: 0, 234 | colors: { 235 | right_front: handlers.Color.empty(), 236 | right_back: handlers.Color.empty(), 237 | left_front: handlers.Color.empty(), 238 | left_back: handlers.Color.empty(), 239 | }, 240 | indicator_red: false, 241 | indicator_green: false, 242 | }); 243 | }); 244 | 245 | it('PID settings default to zero', function () { 246 | expect(handlers.PIDSettings.empty()).toEqual({ 247 | kp: 0, 248 | ki: 0, 249 | kd: 0, 250 | integral_windup_guard: 0, 251 | d_filter_time: 0, 252 | setpoint_filter_time: 0, 253 | command_to_value: 0, 254 | }); 255 | }); 256 | 257 | it('configuration defaults to zeros', function () { 258 | var zeroVector = handlers.Vector3f.empty(); 259 | var zeroLedStateCase = handlers.LEDStateCase.empty(); 260 | var zeroPidSettings = handlers.PIDSettings.empty(); 261 | var config = handlers.Configuration.empty(); 262 | expect(config).toEqual({ 263 | version: { 264 | major: 0, 265 | minor: 0, 266 | patch: 0, 267 | }, 268 | id: 0, 269 | pcb_transform: { 270 | orientation: zeroVector, 271 | translation: zeroVector, 272 | }, 273 | mix_table: { 274 | fz: [0, 0, 0, 0, 0, 0, 0, 0], 275 | tx: [0, 0, 0, 0, 0, 0, 0, 0], 276 | ty: [0, 0, 0, 0, 0, 0, 0, 0], 277 | tz: [0, 0, 0, 0, 0, 0, 0, 0], 278 | }, 279 | mag_bias: { 280 | offset: zeroVector, 281 | }, 282 | channel: { 283 | assignment: {thrust: 0, pitch: 0, roll: 0, yaw: 0, aux1: 0, aux2: 0}, 284 | inversion: {thrust: true, pitch: true, roll: true, yaw: true, aux1: true, aux2: true}, 285 | midpoint: [0, 0, 0, 0, 0, 0], 286 | deadzone: [0, 0, 0, 0, 0, 0], 287 | }, 288 | pid_parameters: { 289 | thrust_master: zeroPidSettings, 290 | pitch_master: zeroPidSettings, 291 | roll_master: zeroPidSettings, 292 | yaw_master: zeroPidSettings, 293 | thrust_slave: zeroPidSettings, 294 | pitch_slave: zeroPidSettings, 295 | roll_slave: zeroPidSettings, 296 | yaw_slave: zeroPidSettings, 297 | thrust_gain: 0, 298 | pitch_gain: 0, 299 | roll_gain: 0, 300 | yaw_gain: 0, 301 | pid_bypass: { 302 | thrust_master: true, 303 | pitch_master: true, 304 | roll_master: true, 305 | yaw_master: true, 306 | thrust_slave: true, 307 | pitch_slave: true, 308 | roll_slave: true, 309 | yaw_slave: true, 310 | }, 311 | }, 312 | state_parameters: { 313 | state_estimation: [0, 0], 314 | enable: [0, 0], 315 | }, 316 | led_states: [ 317 | zeroLedStateCase, 318 | zeroLedStateCase, 319 | zeroLedStateCase, 320 | zeroLedStateCase, 321 | zeroLedStateCase, 322 | zeroLedStateCase, 323 | zeroLedStateCase, 324 | zeroLedStateCase, 325 | zeroLedStateCase, 326 | zeroLedStateCase, 327 | zeroLedStateCase, 328 | zeroLedStateCase, 329 | zeroLedStateCase, 330 | zeroLedStateCase, 331 | zeroLedStateCase, 332 | zeroLedStateCase, 333 | ], 334 | name: '', 335 | }); 336 | expect(config).toEqual(handlers.ConfigurationFixed.empty()); 337 | }); 338 | }); 339 | 340 | describe('1.6.0', function () { 341 | var handlers = null; 342 | beforeEach(function () { 343 | handlers = serializationHandler.getHandler('1.6.0'); 344 | }); 345 | 346 | it('exists', function () { 347 | expect(handlers).toBeDefined(); 348 | }); 349 | 350 | it('has a config', function () { 351 | expect(handlers.Configuration).toBeDefined(); 352 | }); 353 | 354 | it('vector defaults to zeros', function () { 355 | expect(handlers.Vector3f.empty()).toEqual({ 356 | x: 0, 357 | y: 0, 358 | z: 0, 359 | }); 360 | }); 361 | 362 | it('color defaults to zeros', function () { 363 | expect(handlers.Color.empty()).toEqual({ 364 | red: 0, 365 | green: 0, 366 | blue: 0, 367 | }); 368 | }); 369 | 370 | it('status flag defaults to full flag', function () { 371 | expect(handlers.StatusFlag.empty()).toEqual({ 372 | _0: true, 373 | _1: true, 374 | _2: true, 375 | no_signal: true, 376 | idle: true, 377 | arming: true, 378 | recording_sd: true, 379 | _7: true, 380 | loop_slow: true, 381 | _9: true, 382 | armed: true, 383 | battery_low: true, 384 | battery_critical: true, 385 | log_full: true, 386 | crash_detected: true, 387 | override: true, 388 | }); 389 | }); 390 | 391 | it('LED state case defaults to zeros', function () { 392 | expect(handlers.LEDStateCase.empty()).toEqual({ 393 | status: handlers.StatusFlag.empty(), 394 | pattern: 0, 395 | colors: { 396 | right_front: handlers.Color.empty(), 397 | right_back: handlers.Color.empty(), 398 | left_front: handlers.Color.empty(), 399 | left_back: handlers.Color.empty(), 400 | }, 401 | indicator_red: false, 402 | indicator_green: false, 403 | }); 404 | }); 405 | 406 | it('PID settings default to zero', function () { 407 | expect(handlers.PIDSettings.empty()).toEqual({ 408 | kp: 0, 409 | ki: 0, 410 | kd: 0, 411 | integral_windup_guard: 0, 412 | d_filter_time: 0, 413 | setpoint_filter_time: 0, 414 | command_to_value: 0, 415 | }); 416 | }); 417 | 418 | it('configuration defaults to zeros', function () { 419 | var zeroVector = handlers.Vector3f.empty(); 420 | var zeroLedStateCase = handlers.LEDStateCase.empty(); 421 | var zeroPidSettings = handlers.PIDSettings.empty(); 422 | var config = handlers.Configuration.empty(); 423 | expect(config).toEqual({ 424 | version: { 425 | major: 0, 426 | minor: 0, 427 | patch: 0, 428 | }, 429 | id: 0, 430 | pcb_transform: { 431 | orientation: zeroVector, 432 | translation: zeroVector, 433 | }, 434 | mix_table: { 435 | fz: [0, 0, 0, 0, 0, 0, 0, 0], 436 | tx: [0, 0, 0, 0, 0, 0, 0, 0], 437 | ty: [0, 0, 0, 0, 0, 0, 0, 0], 438 | tz: [0, 0, 0, 0, 0, 0, 0, 0], 439 | }, 440 | mag_bias: { 441 | offset: zeroVector, 442 | }, 443 | channel: { 444 | assignment: {thrust: 0, pitch: 0, roll: 0, yaw: 0, aux1: 0, aux2: 0}, 445 | inversion: {thrust: true, pitch: true, roll: true, yaw: true, aux1: true, aux2: true}, 446 | midpoint: [0, 0, 0, 0, 0, 0], 447 | deadzone: [0, 0, 0, 0, 0, 0], 448 | }, 449 | pid_parameters: { 450 | thrust_master: zeroPidSettings, 451 | pitch_master: zeroPidSettings, 452 | roll_master: zeroPidSettings, 453 | yaw_master: zeroPidSettings, 454 | thrust_slave: zeroPidSettings, 455 | pitch_slave: zeroPidSettings, 456 | roll_slave: zeroPidSettings, 457 | yaw_slave: zeroPidSettings, 458 | thrust_gain: 0, 459 | pitch_gain: 0, 460 | roll_gain: 0, 461 | yaw_gain: 0, 462 | pid_bypass: { 463 | thrust_master: true, 464 | pitch_master: true, 465 | roll_master: true, 466 | yaw_master: true, 467 | thrust_slave: true, 468 | pitch_slave: true, 469 | roll_slave: true, 470 | yaw_slave: true, 471 | }, 472 | }, 473 | state_parameters: { 474 | state_estimation: [0, 0], 475 | enable: [0, 0], 476 | }, 477 | led_states: [ 478 | zeroLedStateCase, 479 | zeroLedStateCase, 480 | zeroLedStateCase, 481 | zeroLedStateCase, 482 | zeroLedStateCase, 483 | zeroLedStateCase, 484 | zeroLedStateCase, 485 | zeroLedStateCase, 486 | zeroLedStateCase, 487 | zeroLedStateCase, 488 | zeroLedStateCase, 489 | zeroLedStateCase, 490 | zeroLedStateCase, 491 | zeroLedStateCase, 492 | zeroLedStateCase, 493 | zeroLedStateCase, 494 | ], 495 | name: '', 496 | velocity_pid_parameters: { 497 | forward_master: zeroPidSettings, 498 | right_master: zeroPidSettings, 499 | up_master: zeroPidSettings, 500 | forward_slave: zeroPidSettings, 501 | right_slave: zeroPidSettings, 502 | up_slave: zeroPidSettings, 503 | pid_bypass: { 504 | forward_master: true, 505 | right_master: true, 506 | up_master: true, 507 | _unused_master: true, 508 | forward_slave: true, 509 | right_slave: true, 510 | up_slave: true, 511 | _unused_slave: true, 512 | }, 513 | }, 514 | inertial_bias: { 515 | accel: zeroVector, 516 | gyro: zeroVector, 517 | }, 518 | }); 519 | expect(config).toEqual(handlers.ConfigurationFixed.empty()); 520 | }); 521 | }); 522 | }); 523 | --------------------------------------------------------------------------------