#!/usr/bin/python2.5 # # Copyright 2013 Olivier Gillet. # # Author: Olivier Gillet (ol.gillet@gmail.com) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # See http://creativecommons.org/licenses/MIT/ for more information. # # ----------------------------------------------------------------------------- # # Fsk encoder for converting firmware .bin files into . import numpy import optparse import zlib from stm_audio_bootloader import audio_stream_writer class FskEncoder(object): def __init__( self, sample_rate=48000, pause_period=32, one_period=8, zero_period=4, packet_size=256): self._sr = sample_rate self._pause_period = pause_period self._one_period = one_period self._zero_period = zero_period self._packet_size = packet_size self._state = 1 def _encode(self, symbol_stream): symbol_stream = numpy.array(symbol_stream) counts = [numpy.sum(symbol_stream == symbol) for symbol in range(3)] durations = [self._zero_period, self._one_period, self._pause_period] total_length = numpy.dot(durations, counts) signal = numpy.zeros((total_length, 1)) state = self._state index = 0 for symbol in symbol_stream: d = durations[symbol] signal[index:index + d] = state state = -state index += d assert index == signal.shape[0] self._state = state return signal def _code_blank(self, duration): num_symbols = int(duration * self._sr / self._pause_period) + 1 return self._encode([2] * num_symbols) def _code_packet(self, data): assert len(data) <= self._packet_size if len(data) != self._packet_size: data = data + '\x00' * (self._packet_size - len(data)) crc = zlib.crc32(data) & 0xffffffff data = map(ord, data) crc_bytes = [crc >> 24, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff] bytes = [0x55] * 4 + data + crc_bytes symbol_stream = [] for byte in bytes: mask = 0x80 for _ in range(0, 8): symbol_stream.append(1 if (byte & mask) else 0) mask >>= 1 return self._encode(symbol_stream) def code(self, data, page_size=1024, blank_duration=0.06): yield numpy.zeros((1.0 * self._sr, 1)).ravel() yield self._code_blank(1.0) if len(data) % page_size != 0: tail = page_size - (len(data) % page_size) data += '\xff' * tail offset = 0 remaining_bytes = len(data) num_packets_written = 0 while remaining_bytes: size = min(remaining_bytes, self._packet_size) yield self._code_packet(data[offset:offset+size]) num_packets_written += 1 if num_packets_written == page_size / self._packet_size: yield self._code_blank(blank_duration) num_packets_written = 0 remaining_bytes -= size offset += size yield self._code_blank(1.0) def main(): parser = optparse.OptionParser() parser.add_option( '-s', '--sample_rate', dest='sample_rate', type='int', default=48000, help='Sample rate in Hz') parser.add_option( '-b', '--pause_period', dest='pause_period', type='int', default=64, help='Period (in samples) of a blank symbol') parser.add_option( '-n', '--one_period', dest='one_period', type='int', default=16, help='Period (in samples) of a one symbol') parser.add_option( '-z', '--zero_period', dest='zero_period', type='int', default=8, help='Period (in samples) of a zero symbol') parser.add_option( '-p', '--packet_size', dest='packet_size', type='int', default=256, help='Packet size in bytes') parser.add_option( '-g', '--page_size', dest='page_size', type='int', default=1024, help='Flash page size') parser.add_option( '-k', '--blank_duration', dest='blank_duration', type='int', default=60, help='Duration of the blank between pages, in ms') parser.add_option( '-o', '--output_file', dest='output_file', default=None, help='Write output file to FILE', metavar='FILE') options, args = parser.parse_args() data = file(args[0], 'rb').read() if len(args) != 1: logging.fatal('Specify one, and only one firmware .bin file!') sys.exit(1) output_file = options.output_file if not output_file: if '.bin' in args[0]: output_file = args[0].replace('.bin', '.wav') else: output_file = args[0] + '.wav' encoder = FskEncoder( options.sample_rate, options.pause_period, options.one_period, options.zero_period, options.packet_size) writer = audio_stream_writer.AudioStreamWriter( output_file, options.sample_rate, 16, 1) blank_duration = options.blank_duration * 0.001 for block in encoder.code(data, options.page_size, blank_duration): if len(block): writer.append(block) if __name__ == '__main__': main()