Skip to content

Commit efa2aaa

Browse files
authored
Merge pull request #456 from bitcraze/Toverumar/add_write_param_file
Toverumar/add write param file
2 parents c7eaabe + a0df110 commit efa2aaa

File tree

5 files changed

+267
-0
lines changed

5 files changed

+267
-0
lines changed

cflib/utils/param_file_helper.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# || ____ _ __
4+
# +------+ / __ )(_) /_______________ _____ ___
5+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8+
#
9+
# Copyright (C) 2024 Bitcraze AB
10+
#
11+
# This program is free software; you can redistribute it and/or
12+
# modify it under the terms of the GNU General Public License
13+
# as published by the Free Software Foundation; either version 2
14+
# of the License, or (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
from threading import Event
23+
24+
from cflib.crazyflie import Crazyflie
25+
from cflib.localization.param_io import ParamFileManager
26+
27+
28+
class ParamFileHelper:
29+
'''ParamFileHelper is a helper to synchonously write multiple paramteters
30+
from a file and store them in persistent memory'''
31+
32+
def __init__(self, crazyflie):
33+
if isinstance(crazyflie, Crazyflie):
34+
self._cf = crazyflie
35+
self.persistent_sema = None
36+
self.success = False
37+
else:
38+
raise TypeError('ParamFileHelper only takes a Crazyflie Object')
39+
40+
def _persistent_stored_callback(self, complete_name, success):
41+
self.success = success
42+
if not success:
43+
print(f'Persistent params: failed to store {complete_name}!')
44+
else:
45+
print(f'Persistent params: stored {complete_name}!')
46+
self.persistent_sema.set()
47+
48+
def store_params_from_file(self, filename):
49+
params = ParamFileManager().read(filename)
50+
for param, state in params.items():
51+
self.persistent_sema = Event()
52+
self._cf.param.set_value(param, state.stored_value)
53+
self._cf.param.persistent_store(param, self._persistent_stored_callback)
54+
self.persistent_sema.wait()
55+
if not self.success:
56+
break
57+
return self.success
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# ,---------, ____ _ __
4+
# | ,-^-, | / __ )(_) /_______________ _____ ___
5+
# | ( O ) | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6+
# | / ,--' | / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7+
# +------` /_____/_/\__/\___/_/ \__,_/ /___/\___/
8+
#
9+
# Copyright (C) 2024 Bitcraze AB
10+
#
11+
# This program is free software: you can redistribute it and/or modify
12+
# it under the terms of the GNU General Public License as published by
13+
# the Free Software Foundation, in version 3.
14+
#
15+
# This program is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22+
"""
23+
Example to show how to write several persistent parameters from a yaml file.
24+
The params in the file should be formatted like this;
25+
26+
params:
27+
activeMarker.back:
28+
default_value: 3
29+
is_stored: true
30+
stored_value: 30
31+
type: persistent_param_state
32+
version: '1'
33+
"""
34+
import argparse
35+
import logging
36+
37+
import cflib.crtp
38+
from cflib.crazyflie import Crazyflie
39+
from cflib.crazyflie.syncCrazyflie import SyncCrazyflie
40+
from cflib.utils import uri_helper
41+
from cflib.utils.param_file_helper import ParamFileHelper
42+
43+
44+
uri = uri_helper.uri_from_env(default='radio://0/80/2M/E7E7E7E7E7')
45+
46+
# Only output errors from the logging framework
47+
logging.basicConfig(level=logging.ERROR)
48+
49+
50+
if __name__ == '__main__':
51+
parser = argparse.ArgumentParser()
52+
parser.add_argument('-f', '--file', type=str, help='The yaml file containing the arguments. ')
53+
args = parser.parse_args()
54+
55+
cflib.crtp.init_drivers()
56+
57+
cf = Crazyflie(rw_cache='./cache')
58+
with SyncCrazyflie(uri, cf=cf) as scf:
59+
writer = ParamFileHelper(scf.cf)
60+
writer.store_params_from_file(args.file)

test/utils/fixtures/five_params.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
params:
2+
activeMarker.back:
3+
default_value: 3
4+
is_stored: true
5+
stored_value: 10
6+
activeMarker.front:
7+
default_value: 3
8+
is_stored: true
9+
stored_value: 10
10+
activeMarker.left:
11+
default_value: 3
12+
is_stored: true
13+
stored_value: 10
14+
cppm.angPitch:
15+
default_value: 50.0
16+
is_stored: true
17+
stored_value: 55.0
18+
ctrlMel.i_range_z:
19+
default_value: 0.4000000059604645
20+
is_stored: true
21+
stored_value: 0.44999998807907104
22+
type: persistent_param_state
23+
version: '1'

test/utils/fixtures/single_param.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
params:
2+
activeMarker.back:
3+
default_value: 3
4+
is_stored: true
5+
stored_value: 10
6+
type: persistent_param_state
7+
version: '1'

test/utils/test_param_file_helper.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# || ____ _ __
4+
# +------+ / __ )(_) /_______________ _____ ___
5+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
6+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
7+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
8+
#
9+
# Copyright (C) 2018 Bitcraze AB
10+
#
11+
# This program is free software; you can redistribute it and/or
12+
# modify it under the terms of the GNU General Public License
13+
# as published by the Free Software Foundation; either version 2
14+
# of the License, or (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU General Public License for more details.
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
import unittest
23+
from threading import Event
24+
from unittest.mock import MagicMock
25+
from unittest.mock import patch
26+
27+
from cflib.crazyflie import Crazyflie
28+
from cflib.crazyflie.syncCrazyflie import SyncCrazyflie
29+
from cflib.utils.param_file_helper import ParamFileHelper
30+
31+
32+
class ParamFileHelperTests(unittest.TestCase):
33+
34+
def setUp(self):
35+
self.cf_mock = MagicMock(spec=Crazyflie)
36+
self.helper = ParamFileHelper(self.cf_mock)
37+
38+
def test_ParamFileHelper_SyncCrazyflieAsParam_ThrowsException(self):
39+
cf_mock = MagicMock(spec=SyncCrazyflie)
40+
helper = None
41+
try:
42+
helper = ParamFileHelper(cf_mock)
43+
except Exception:
44+
self.assertIsNone(helper)
45+
else:
46+
self.fail('Expect exception')
47+
48+
def test_ParamFileHelper_Crazyflie_Object(self):
49+
helper = ParamFileHelper(self.cf_mock)
50+
self.assertIsNotNone(helper)
51+
52+
@patch('cflib.crazyflie.Param')
53+
def test_ParamFileHelper_writesAndStoresParamFromFileToCrazyflie(self, mock_Param):
54+
# Setup
55+
cf_mock = MagicMock(spec=Crazyflie)
56+
cf_mock.param = mock_Param
57+
helper = ParamFileHelper(cf_mock)
58+
# Mock blocking wait and call callback instead. This lets the flow work as it would in the asynch world
59+
60+
def mock_wait(self, timeout=None):
61+
helper._persistent_stored_callback('activeMarker.back', True)
62+
return
63+
64+
with patch.object(Event, 'wait', new=mock_wait):
65+
self.assertTrue(helper.store_params_from_file('test/utils/fixtures/single_param.yaml'))
66+
mock_Param.set_value.assert_called_once_with('activeMarker.back', 10)
67+
mock_Param.persistent_store.assert_called_once_with('activeMarker.back', helper._persistent_stored_callback)
68+
69+
@patch('cflib.crazyflie.Param')
70+
def test_ParamFileHelper_writesParamAndFailsToSetPersistantShouldReturnFalse(self, mock_Param):
71+
# Setup
72+
cf_mock = MagicMock(spec=Crazyflie)
73+
cf_mock.param = mock_Param
74+
helper = ParamFileHelper(cf_mock)
75+
# Mock blocking wait and call callback instead. This lets the flow work as it would in the asynch world
76+
77+
def mock_wait(self, timeout=None):
78+
helper._persistent_stored_callback('activeMarker.back', False)
79+
return
80+
81+
with patch.object(Event, 'wait', new=mock_wait):
82+
self.assertFalse(helper.store_params_from_file('test/utils/fixtures/single_param.yaml'))
83+
mock_Param.set_value.assert_called_once_with('activeMarker.back', 10)
84+
mock_Param.persistent_store.assert_called_once_with('activeMarker.back', helper._persistent_stored_callback)
85+
86+
@patch('cflib.crazyflie.Param')
87+
def test_ParamFileHelper_TryWriteSeveralParamsPersistantShouldBreakAndReturnFalse(self, mock_Param):
88+
# Setup
89+
cf_mock = MagicMock(spec=Crazyflie)
90+
cf_mock.param = mock_Param
91+
helper = ParamFileHelper(cf_mock)
92+
# Mock blocking wait and call callback instead. This lets the flow work as it would in the asynch world
93+
94+
def mock_wait(self, timeout=None):
95+
helper._persistent_stored_callback('activeMarker.back', False)
96+
return
97+
98+
with patch.object(Event, 'wait', new=mock_wait):
99+
# Test and assert
100+
self.assertFalse(helper.store_params_from_file('test/utils/fixtures/five_params.yaml'))
101+
# Assert it breaks directly by checking number of calls
102+
mock_Param.set_value.assert_called_once_with('activeMarker.back', 10)
103+
mock_Param.persistent_store.assert_called_once_with('activeMarker.back', helper._persistent_stored_callback)
104+
105+
@patch('cflib.crazyflie.Param')
106+
def test_ParamFileHelper_writesAndStoresAllParamsFromFileToCrazyflie(self, mock_Param):
107+
# Setup
108+
cf_mock = MagicMock(spec=Crazyflie)
109+
cf_mock.param = mock_Param
110+
helper = ParamFileHelper(cf_mock)
111+
# Mock blocking wait and call callback instead. This lets the flow work as it would in the asynch world
112+
113+
def mock_wait(self, timeout=None):
114+
helper._persistent_stored_callback('something', True)
115+
return
116+
with patch.object(Event, 'wait', new=mock_wait):
117+
# Test and Assert
118+
self.assertTrue(helper.store_params_from_file('test/utils/fixtures/five_params.yaml'))
119+
self.assertEquals(5, len(mock_Param.set_value.mock_calls))
120+
self.assertEquals(5, len(mock_Param.persistent_store.mock_calls))

0 commit comments

Comments
 (0)