si4703 0.1.0

Platform-agnostic Rust driver for the Si4703 and Si4702 FM radio turners (receivers).
Documentation
use embedded_hal_mock::i2c::Transaction as I2cTrans;
use si4703::{
    fill_with_rds_radio_text, get_rds_radio_text, RdsBlockData, RdsBlockErrors, RdsData, RdsMode,
    RdsRadioText, RdsRadioTextData,
};

mod common;
use self::common::{destroy, new_si4703, BitFlags as BF, DEV_ADDR};

write_test!(en_rds_std, BF::RDS, 16, 3, enable_rds, RdsMode::Standard);

#[test]
fn can_enable_rds_verbose() {
    let transactions = [
        I2cTrans::read(DEV_ADDR, [0; 32].to_vec()),
        I2cTrans::write(
            DEV_ADDR,
            vec![
                (BF::RDSM >> 8) as u8,
                BF::RDSM as u8,
                0,
                0,
                (BF::RDS >> 8) as u8,
                BF::RDS as u8,
            ],
        ),
    ];
    let mut dev = new_si4703(&transactions);
    dev.enable_rds(RdsMode::Verbose).unwrap();
    destroy(dev);
}

write_test!(en_rds_int, BF::RDSIEN, 16, 3, enable_rds_interrupts);
write_test!(dis_rds_int, 0, 16, 3, disable_rds_interrupts);

read_test!(rds_not_sync, 0, 2, false, rds_synchronized);
read_test!(rds_sync, BF::RDSS, 2, true, rds_synchronized);

read_test!(rds_not_ready, 0, 2, false, rds_ready);
read_test!(rds_ready, BF::RDSR, 2, true, rds_ready);

#[test]
fn get_rds_data() {
    let rds_data = RdsData {
        a: RdsBlockData {
            data: 0x1234,
            errors: RdsBlockErrors::OneOrTwo,
        },
        b: RdsBlockData {
            data: 0x5678,
            errors: RdsBlockErrors::ThreeToFive,
        },
        c: RdsBlockData {
            data: 0x9ABC,
            errors: RdsBlockErrors::OneOrTwo,
        },
        d: RdsBlockData {
            data: 0xDEF0,
            errors: RdsBlockErrors::TooMany,
        },
    };
    let bler_bcd = BF::BLERB1 | BF::BLERC0 | BF::BLERD0 | BF::BLERD1;
    let mut data = [0; 12];
    data[0] = (BF::BLERA0 >> 8) as u8;
    data[1] = BF::BLERA0 as u8;
    data[2] = (bler_bcd >> 8) as u8;
    data[3] = bler_bcd as u8;
    data[4] = (rds_data.a.data >> 8) as u8;
    data[5] = rds_data.a.data as u8;
    data[6] = (rds_data.b.data >> 8) as u8;
    data[7] = rds_data.b.data as u8;
    data[8] = (rds_data.c.data >> 8) as u8;
    data[9] = rds_data.c.data as u8;
    data[10] = (rds_data.d.data >> 8) as u8;
    data[11] = rds_data.d.data as u8;
    let transactions = [I2cTrans::read(DEV_ADDR, data.to_vec())];
    let mut dev = new_si4703(&transactions);
    assert_eq!(rds_data, dev.rds_data().unwrap());
    destroy(dev);
}

// It is not possible to call default() for consts
const IRRELEVANT: RdsBlockData = RdsBlockData {
    data: 0,
    errors: RdsBlockErrors::None,
};

const DATA_AB: RdsData = RdsData {
    a: IRRELEVANT,
    b: RdsBlockData {
        data: 0x2800,
        errors: RdsBlockErrors::OneOrTwo,
    },
    c: IRRELEVANT,
    d: RdsBlockData {
        data: 0x4142,
        errors: RdsBlockErrors::OneOrTwo,
    },
};

const DATA_ABCD: RdsData = RdsData {
    a: IRRELEVANT,
    b: RdsBlockData {
        data: 0x2000,
        errors: RdsBlockErrors::OneOrTwo,
    },
    c: RdsBlockData {
        data: 0x4142,
        errors: RdsBlockErrors::ThreeToFive,
    },
    d: RdsBlockData {
        data: 0x4344,
        errors: RdsBlockErrors::OneOrTwo,
    },
};

const NULL_TEXT: RdsRadioTextData = RdsRadioTextData::Four('\0', '\0', '\0', '\0');

mod get_rds_radio_text {
    use super::*;

    #[test]
    fn unsupported_number_of_errors_in_block_b() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::ThreeToFive,
            },
            ..Default::default()
        };
        assert_eq!(None, get_rds_radio_text(&data));
    }

    #[test]
    fn different_type() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x3000,
                errors: RdsBlockErrors::OneOrTwo,
            },
            ..Default::default()
        };
        assert_eq!(None, get_rds_radio_text(&data));
    }

    #[test]
    fn erroneous_data_c_for_four() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2000,
                errors: RdsBlockErrors::OneOrTwo,
            },
            c: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::TooMany,
            },
            ..Default::default()
        };
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: None
            }),
            get_rds_radio_text(&data)
        );
    }

    #[test]
    fn erroneous_data_d_for_four() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2000,
                errors: RdsBlockErrors::OneOrTwo,
            },
            d: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::TooMany,
            },
            ..Default::default()
        };
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: None
            }),
            get_rds_radio_text(&data)
        );
    }

    #[test]
    fn read_four() {
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: Some((RdsRadioTextData::Four('A', 'B', 'C', 'D'), 0)),
            }),
            get_rds_radio_text(&DATA_ABCD)
        );
    }

    #[test]
    fn can_get_screen_clear() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2010,
                errors: RdsBlockErrors::OneOrTwo,
            },
            ..Default::default()
        };
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: true,
                text: Some((NULL_TEXT, 0)),
            }),
            get_rds_radio_text(&data)
        );
    }

    #[test]
    fn can_get_offset() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2009,
                errors: RdsBlockErrors::OneOrTwo,
            },
            ..Default::default()
        };
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: Some((NULL_TEXT, 9 * 4)),
            }),
            get_rds_radio_text(&data)
        );
    }

    #[test]
    fn read_two() {
        let mut data = DATA_AB;
        data.b.data += 9;
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: Some((RdsRadioTextData::Two('A', 'B'), 9 * 2)),
            }),
            get_rds_radio_text(&data)
        );
    }

    #[test]
    fn erroneous_data_d_for_two() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2800,
                errors: RdsBlockErrors::OneOrTwo,
            },
            d: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::TooMany,
            },
            ..Default::default()
        };
        assert_eq!(
            Some(RdsRadioText {
                screen_clear: false,
                text: None
            }),
            get_rds_radio_text(&data)
        );
    }
}

mod fill_radio_text {
    use super::*;
    const EMPTY: [char; 64] = ['_'; 64];

    macro_rules! array_eq {
        ($value:expr, $expected:expr) => {
            assert_eq!($expected[..32], $value[..32]);
            assert_eq!($expected[32..], $value[32..]);
        };
    }

    #[test]
    fn empty_data() {
        let data = RdsData::default();
        let mut text = EMPTY;
        assert_eq!(false, fill_with_rds_radio_text(&mut text, &data));
        array_eq!(EMPTY, text);
    }

    #[test]
    fn erroneous_data() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2000,
                errors: RdsBlockErrors::OneOrTwo,
            },
            d: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::TooMany,
            },
            ..Default::default()
        };
        let mut text = EMPTY;
        assert_eq!(false, fill_with_rds_radio_text(&mut text, &data));
        array_eq!(EMPTY, text);
    }

    #[test]
    fn can_get_screen_clear_on_erroneous_data() {
        let data = RdsData {
            b: RdsBlockData {
                data: 0x2010,
                errors: RdsBlockErrors::OneOrTwo,
            },
            d: RdsBlockData {
                data: 0,
                errors: RdsBlockErrors::TooMany,
            },
            ..Default::default()
        };
        let mut text = EMPTY;
        assert!(fill_with_rds_radio_text(&mut text, &data));
        array_eq!(EMPTY, text);
    }

    #[test]
    fn can_read_two() {
        let mut text = EMPTY;
        assert_eq!(false, fill_with_rds_radio_text(&mut text, &DATA_AB));
        let mut expected = EMPTY;
        expected[0..2].copy_from_slice(&['A', 'B']);
        array_eq!(expected, text);
    }

    #[test]
    fn can_read_four() {
        let mut text = EMPTY;
        assert_eq!(false, fill_with_rds_radio_text(&mut text, &DATA_ABCD));
        let mut expected = EMPTY;
        expected[0..4].copy_from_slice(&['A', 'B', 'C', 'D']);
        array_eq!(expected, text);
    }

    #[test]
    fn can_get_screen_clear() {
        let mut text = EMPTY;
        let mut data = DATA_AB;
        data.b.data += 0x10;
        assert!(fill_with_rds_radio_text(&mut text, &data));
    }
}