mirror of
https://github.com/oliexdev/openScale.git
synced 2025-02-07 01:21:09 +01:00
569 lines
13 KiB
C++
569 lines
13 KiB
C++
/* Copyright (C) 2014 olie.xdev <olie.xdev@googlemail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
#include <LowPower.h>
|
|
#include <RunningMedian.h>
|
|
#include <Wire.h>
|
|
#include <Time.h>
|
|
#include <DS3232RTC.h>
|
|
#include <I2C_eeprom.h>
|
|
|
|
#define SEG_1_1 4
|
|
#define SEG_1_2 5
|
|
#define SEG_2_1 6
|
|
#define SEG_2_2 7
|
|
#define SEG_3_1 8
|
|
#define SEG_3_2 9
|
|
#define SEG_4_1 10
|
|
#define SEG_4_2 11
|
|
#define UP 12
|
|
#define C0 A0
|
|
#define C1 A1
|
|
#define C2 A2
|
|
#define C3 A3
|
|
#define WAKEUP_PIN 3
|
|
#define EXT_SWITCH_PIN 13
|
|
|
|
#define MAX_SAMPLE_SIZE 6
|
|
#define MAX_NO_ACTIVITY_CYCLES 32
|
|
|
|
I2C_eeprom eeprom(0x50);
|
|
|
|
char port_control;
|
|
char port_digital_pinA;
|
|
char port_digital_pinB;
|
|
|
|
int control_bit[4];
|
|
|
|
int seg_raw_1_1[4];
|
|
int seg_raw_1_2[4];
|
|
int seg_raw_2_1[4];
|
|
int seg_raw_2_2[4];
|
|
int seg_raw_3_1[4];
|
|
int seg_raw_3_2[4];
|
|
int seg_raw_4_1[4];
|
|
int seg_raw_4_2[4];
|
|
|
|
char seg_value_1;
|
|
char seg_value_2;
|
|
char seg_value_3;
|
|
char seg_value_4;
|
|
|
|
RunningMedian<char, MAX_SAMPLE_SIZE> seg_samples_1;
|
|
RunningMedian<char, MAX_SAMPLE_SIZE> seg_samples_2;
|
|
RunningMedian<char, MAX_SAMPLE_SIZE> seg_samples_3;
|
|
RunningMedian<char, MAX_SAMPLE_SIZE> seg_samples_4;
|
|
|
|
int sample_count = 0;
|
|
|
|
int no_activity_cycles = 0;
|
|
|
|
volatile boolean sleep_state = true;
|
|
|
|
int measured_user_id = -1;
|
|
int measured_weight = -1;
|
|
int measured_fat = -1;
|
|
int measured_water = -1;
|
|
int measured_muscle = -1;
|
|
|
|
typedef struct scale_data{
|
|
byte user_id;
|
|
int year;
|
|
byte month;
|
|
byte day;
|
|
byte hour;
|
|
byte minute;
|
|
int weight;
|
|
int fat;
|
|
int water;
|
|
int muscle;
|
|
int checksum;
|
|
} __attribute__ ((packed)); // avoiding byte padding in this struct. Important for continuous writing/reading to/from eeprom!
|
|
|
|
|
|
void interrupt_handler()
|
|
{
|
|
sleep_state = false;
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(9600);
|
|
|
|
pinMode(SEG_1_1, INPUT);
|
|
pinMode(SEG_1_2, INPUT);
|
|
pinMode(SEG_2_1, INPUT);
|
|
pinMode(SEG_2_2, INPUT);
|
|
pinMode(SEG_3_1, INPUT);
|
|
pinMode(SEG_3_2, INPUT);
|
|
pinMode(SEG_4_1, INPUT);
|
|
pinMode(SEG_4_2, INPUT);
|
|
pinMode(UP, OUTPUT);
|
|
pinMode(C0, INPUT);
|
|
pinMode(C1, INPUT);
|
|
pinMode(C2, INPUT);
|
|
pinMode(C3, INPUT);
|
|
pinMode(WAKEUP_PIN, INPUT);
|
|
pinMode(EXT_SWITCH_PIN, OUTPUT);
|
|
|
|
digitalWrite(EXT_SWITCH_PIN, HIGH);
|
|
}
|
|
|
|
void set_seg_raw(int cycle_n)
|
|
{
|
|
seg_raw_1_1[cycle_n] = (port_digital_pinA & (1 << 4)) ? 1 : 0;
|
|
seg_raw_1_2[cycle_n] = (port_digital_pinA & (1 << 5)) ? 1 : 0;
|
|
seg_raw_2_1[cycle_n] = (port_digital_pinA & (1 << 6)) ? 1 : 0;
|
|
seg_raw_2_2[cycle_n] = (port_digital_pinA & (1 << 7)) ? 1 : 0;
|
|
seg_raw_3_1[cycle_n] = (port_digital_pinB & (1 << 0)) ? 1 : 0;
|
|
seg_raw_3_2[cycle_n] = (port_digital_pinB & (1 << 1)) ? 1 : 0;
|
|
seg_raw_4_1[cycle_n] = (port_digital_pinB & (1 << 2)) ? 1 : 0;
|
|
seg_raw_4_2[cycle_n] = (port_digital_pinB & (1 << 3)) ? 1 : 0;
|
|
}
|
|
|
|
char decode_seg(int seg_x[4], int seg_y[4])
|
|
{
|
|
boolean b = seg_x[0];
|
|
boolean c = seg_x[1];
|
|
boolean e = seg_x[2];
|
|
boolean f = seg_x[3];
|
|
boolean a = seg_y[0];
|
|
boolean d = seg_y[1];
|
|
boolean g = seg_y[2];
|
|
boolean x = seg_y[3];
|
|
|
|
if (!e && !c && !b && !f &&
|
|
!g && !d && !a)
|
|
return ' ';
|
|
|
|
if (e && !c && b && f &&
|
|
g && d && a)
|
|
return '0';
|
|
|
|
if (e && !c && b && !f &&
|
|
!g && !d && !a)
|
|
return '1';
|
|
|
|
if (!e && c && b && f &&
|
|
g && !d && a)
|
|
return '2';
|
|
|
|
if (e && c && b && f &&
|
|
!g && !d && a)
|
|
return '3';
|
|
|
|
if (e && c && b && !f &&
|
|
!g && d && !a)
|
|
return '4';
|
|
|
|
if (e && c && !b && f &&
|
|
!g && d && a)
|
|
return '5';
|
|
|
|
if (e && c && !b && f &&
|
|
g && d && a)
|
|
return '6';
|
|
|
|
if (e && !c && b && !f &&
|
|
!g && !d && a)
|
|
return '7';
|
|
|
|
if (e && c && b && f &&
|
|
g && d && a)
|
|
return '8';
|
|
|
|
if (e && c && b && f &&
|
|
!g && d && a)
|
|
return '9';
|
|
|
|
if (!e && c && !b && !f &&
|
|
!g && !d && !a)
|
|
return '-';
|
|
|
|
if (!e && c && b && !f &&
|
|
g && d && a)
|
|
return 'P';
|
|
|
|
if (e && !c && b && !f &&
|
|
g && d && a)
|
|
return 'M';
|
|
|
|
if (!e && c && !b && f &&
|
|
g && d && a)
|
|
return 'E';
|
|
|
|
if (!e && c && !b && !f &&
|
|
g && d && a)
|
|
return 'F';
|
|
|
|
if (e && c && b && !f &&
|
|
g && d && !a)
|
|
return 'H';
|
|
|
|
return -1;
|
|
}
|
|
|
|
void before_sleep_event()
|
|
{
|
|
Serial.println("$I$ going to sleep in 3 seconds!");
|
|
|
|
if (measured_user_id != -1 && measured_weight != -1 && measured_fat != -1 && measured_water != -1 && measured_muscle != -1) {
|
|
write_scale_data(measured_user_id, measured_weight, measured_fat, measured_water, measured_muscle);
|
|
delay(100);
|
|
}
|
|
|
|
send_scale_data();
|
|
|
|
delay(3000);
|
|
|
|
digitalWrite(EXT_SWITCH_PIN, LOW);
|
|
}
|
|
|
|
void after_sleep_event()
|
|
{
|
|
digitalWrite(EXT_SWITCH_PIN, HIGH);
|
|
|
|
measured_user_id = -1;
|
|
measured_weight = -1;
|
|
measured_fat = -1;
|
|
measured_water = -1;
|
|
measured_muscle = -1;
|
|
|
|
delay(4000);
|
|
digitalWrite(UP, HIGH);
|
|
delay(500);
|
|
digitalWrite(UP, LOW);
|
|
|
|
setSyncProvider(RTC.get);
|
|
if (timeStatus() != timeSet) {
|
|
Serial.println("$E$ Can't sync to RTC clock!");
|
|
} else {
|
|
Serial.println("$I$ Successful sync to RTC clock");
|
|
}
|
|
|
|
Serial.print("$I$ Time: ");
|
|
Serial.print(hour());
|
|
Serial.write(':');
|
|
Serial.print(minute());
|
|
Serial.write(':');
|
|
Serial.print(second());
|
|
Serial.print(" Date: ");
|
|
Serial.print(day());
|
|
Serial.write('/');
|
|
Serial.print(month());
|
|
Serial.write('/');
|
|
Serial.print(year());
|
|
Serial.println();
|
|
|
|
Serial.println("$I$ openScale MCU ready!");
|
|
}
|
|
|
|
|
|
void check_display_activity()
|
|
{
|
|
if (no_activity_cycles > MAX_NO_ACTIVITY_CYCLES)
|
|
{
|
|
sleep_state = true;
|
|
no_activity_cycles = 0;
|
|
}
|
|
|
|
|
|
if (sleep_state == true)
|
|
{
|
|
before_sleep_event();
|
|
|
|
// Allow wake up pin to trigger interrupt on rising edge.
|
|
attachInterrupt(1, interrupt_handler, RISING);
|
|
|
|
// Enter power down state with ADC and BOD module disabled.
|
|
// Wake up when wake up pin is rising.
|
|
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
|
|
|
|
// Disable external pin interrupt on wake up pin.
|
|
detachInterrupt(1);
|
|
|
|
after_sleep_event();
|
|
}
|
|
}
|
|
|
|
int calc_checksum(struct scale_data* wdata)
|
|
{
|
|
int checksum = 0;
|
|
|
|
checksum ^= wdata->user_id;
|
|
checksum ^= wdata->year;
|
|
checksum ^= wdata->month;
|
|
checksum ^= wdata->day;
|
|
checksum ^= wdata->hour;
|
|
checksum ^= wdata->minute;
|
|
checksum ^= (int)((float)wdata->weight / 10.0f);
|
|
checksum ^= (int)((float)wdata->fat / 10.0f);
|
|
checksum ^= (int)((float)wdata->water / 10.0f);
|
|
checksum ^= (int)((float)wdata->muscle / 10.0f);
|
|
|
|
return checksum;
|
|
}
|
|
|
|
void write_scale_data(int user_id, int weight, int fat, int water, int muscle)
|
|
{
|
|
int data_size = 0;
|
|
struct scale_data wdata;
|
|
|
|
eeprom.readBlock(0, (uint8_t*)&data_size, sizeof(data_size));
|
|
|
|
wdata.user_id = user_id;
|
|
wdata.year = year();
|
|
wdata.month = month();
|
|
wdata.day = day();
|
|
wdata.hour = hour();
|
|
wdata.minute = minute();
|
|
wdata.weight = weight;
|
|
wdata.fat = fat;
|
|
wdata.water = water;
|
|
wdata.muscle = muscle;
|
|
wdata.checksum = calc_checksum(&wdata);
|
|
|
|
if (eeprom.writeBlock(sizeof(data_size)+data_size*sizeof(wdata), (uint8_t*)&wdata, sizeof(wdata)) != 0) {
|
|
Serial.println("$E$ Error writing data to eeprom");
|
|
}
|
|
|
|
delay(100);
|
|
data_size++;
|
|
|
|
if (eeprom.writeBlock(0, (uint8_t*)&data_size, sizeof(data_size)) != 0) {
|
|
Serial.println("$E$ Error writing data to eeprom");
|
|
}
|
|
}
|
|
|
|
void send_scale_data()
|
|
{
|
|
int data_size = 0;
|
|
struct scale_data wdata;
|
|
|
|
eeprom.readBlock(0, (uint8_t*)&data_size, sizeof(data_size));
|
|
|
|
Serial.print("$S$");
|
|
Serial.println(data_size);
|
|
|
|
for (int i=0; i < data_size; i++)
|
|
{
|
|
eeprom.readBlock(sizeof(data_size)+i*sizeof(wdata), (uint8_t*)&wdata, sizeof(wdata));
|
|
|
|
if (wdata.checksum != calc_checksum(&wdata)) {
|
|
Serial.print("$E$ Wrong Checksum for data ");
|
|
Serial.print(i);
|
|
Serial.println();
|
|
}
|
|
|
|
Serial.print("$D$");
|
|
Serial.print(wdata.user_id);
|
|
Serial.print(',');
|
|
Serial.print(wdata.year);
|
|
Serial.print(',');
|
|
Serial.print(wdata.month);
|
|
Serial.print(',');
|
|
Serial.print(wdata.day);
|
|
Serial.print(',');
|
|
Serial.print(wdata.hour);
|
|
Serial.print(',');
|
|
Serial.print(wdata.minute);
|
|
Serial.print(',');
|
|
Serial.print((float)wdata.weight / 10.0f);
|
|
Serial.print(',');
|
|
Serial.print((float)wdata.fat / 10.0f);
|
|
Serial.print(',');
|
|
Serial.print((float)wdata.water / 10.0f);
|
|
Serial.print(',');
|
|
Serial.print((float)wdata.muscle / 10.0f);
|
|
Serial.print(',');
|
|
Serial.print(wdata.checksum);
|
|
Serial.print('\n');
|
|
}
|
|
|
|
}
|
|
|
|
void clear_scale_data()
|
|
{
|
|
int data_size = 0;
|
|
eeprom.writeBlock(0, (uint8_t*)&data_size, sizeof(data_size));
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
check_display_activity();
|
|
|
|
port_control = PINC;
|
|
port_digital_pinA = PIND;
|
|
port_digital_pinB = PINB;
|
|
|
|
control_bit[0] = (port_control & (1 << 0)) ? 1 : 0;
|
|
control_bit[1] = (port_control & (1 << 1)) ? 1 : 0;
|
|
control_bit[2] = (port_control & (1 << 2)) ? 1 : 0;
|
|
control_bit[3] = (port_control & (1 << 3)) ? 1 : 0;
|
|
|
|
if (control_bit[0] == LOW && control_bit[1] == HIGH && control_bit[2] == HIGH && control_bit[3] == HIGH)
|
|
{
|
|
set_seg_raw(0);
|
|
|
|
}
|
|
else if (control_bit[0] == HIGH && control_bit[1] == LOW && control_bit[2] == HIGH && control_bit[3] == HIGH)
|
|
{
|
|
set_seg_raw(1);
|
|
|
|
}
|
|
else if (control_bit[0] == HIGH && control_bit[1] == HIGH && control_bit[2] == LOW && control_bit[3] == HIGH)
|
|
{
|
|
set_seg_raw(2);
|
|
|
|
}
|
|
else if (control_bit[0] == HIGH && control_bit[1] == HIGH && control_bit[2] == HIGH && control_bit[3] == LOW)
|
|
{
|
|
set_seg_raw(3);
|
|
|
|
}
|
|
else if (control_bit[0] == HIGH && control_bit[1] == HIGH && control_bit[2] == HIGH && control_bit[3] == HIGH)
|
|
{
|
|
no_activity_cycles++;
|
|
}
|
|
|
|
seg_value_1 = decode_seg(seg_raw_1_1, seg_raw_1_2);
|
|
seg_value_2 = decode_seg(seg_raw_2_1, seg_raw_2_2);
|
|
seg_value_3 = decode_seg(seg_raw_3_1, seg_raw_3_2);
|
|
seg_value_4 = decode_seg(seg_raw_4_1, seg_raw_4_2);
|
|
|
|
if (seg_value_1 != -1 && seg_value_2 != -1 && seg_value_3 != -1 && seg_value_4 != -1)
|
|
{
|
|
seg_samples_1.add(seg_value_1);
|
|
seg_samples_2.add(seg_value_2);
|
|
seg_samples_3.add(seg_value_3);
|
|
seg_samples_4.add(seg_value_4);
|
|
|
|
sample_count++;
|
|
}
|
|
|
|
|
|
if (sample_count > MAX_SAMPLE_SIZE)
|
|
{
|
|
seg_samples_1.getMedian(seg_value_1);
|
|
seg_samples_2.getMedian(seg_value_2);
|
|
seg_samples_3.getMedian(seg_value_3);
|
|
seg_samples_4.getMedian(seg_value_4);
|
|
|
|
if (seg_value_4 == ' ') {
|
|
measured_weight = char_to_int(seg_value_1) + char_to_int(seg_value_2)*10 + char_to_int(seg_value_3)*100;
|
|
}
|
|
|
|
if (seg_value_4 == 'F') {
|
|
measured_fat = char_to_int(seg_value_1) + char_to_int(seg_value_2)*10 + char_to_int(seg_value_3)*100;
|
|
}
|
|
|
|
if (seg_value_4 == 'H') {
|
|
measured_water = char_to_int(seg_value_1) + char_to_int(seg_value_2)*10 + char_to_int(seg_value_3)*100;
|
|
}
|
|
|
|
if (seg_value_4 == 'M') {
|
|
measured_muscle = char_to_int(seg_value_1) + char_to_int(seg_value_2)*10 + char_to_int(seg_value_3)*100;
|
|
}
|
|
|
|
if (seg_value_4 == 'P') {
|
|
measured_user_id = char_to_int(seg_value_1) + char_to_int(seg_value_2)*10;
|
|
}
|
|
|
|
sample_count = 0;
|
|
}
|
|
|
|
delay(10);
|
|
|
|
|
|
if (Serial.available() > 0)
|
|
{
|
|
char command = Serial.read();
|
|
|
|
switch(command)
|
|
{
|
|
case '0':
|
|
Serial.println("$I$ openScale MCU Version 1.0");
|
|
break;
|
|
case '1':
|
|
Serial.println("$I$ Sending scale data!");
|
|
send_scale_data();
|
|
break;
|
|
case '9':
|
|
clear_scale_data();
|
|
Serial.println("$I$ Scale data cleared!");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int char_to_int(char c)
|
|
{
|
|
if (c == ' ')
|
|
return 0;
|
|
|
|
return (c - '0');
|
|
}
|
|
|
|
void print_debug_output()
|
|
{
|
|
Serial.print("Debug output\n");
|
|
Serial.print("-----------------------------------\n");
|
|
Serial.print("\nSeg 1\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_1_1[i]);
|
|
}
|
|
Serial.print("\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_1_2[i]);
|
|
}
|
|
|
|
Serial.print("\nSeg 2\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_2_1[i]);
|
|
}
|
|
Serial.print("\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_2_2[i]);
|
|
}
|
|
|
|
Serial.print("\nSeg 3\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_3_1[i]);
|
|
}
|
|
Serial.print("\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_3_2[i]);
|
|
}
|
|
|
|
Serial.print("\nSeg 4\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_4_1[i]);
|
|
}
|
|
Serial.print("\n");
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
Serial.print(seg_raw_4_2[i]);
|
|
}
|
|
|
|
Serial.print("\n-----------------------------------\n");
|
|
}
|