Samstag, 5. Januar 2013

Time machine: Release One.



Camera Control with Arduino.
Myths and realities Х-sync with studio light.
Time machine: Display.
Time machine: Power.
Time machine: Microphone.
Time machine: Analog.
Time machine: Encoder.

Шаг за шагом я добрался до готового устройства. Схема закончена, платы заказаны, базовый код написан, ошибки найдены и исправлены. Вспоминая закон «Мерфи» в это поверить трудно. Но пока так и останется. Если ошибки и есть, то это будут не ошибки, а скрытые возможности.

Подведём итоги:

Питается всё от батарейки 9В. Аккумулятора 200мАч должно хватить на 7 часов работы, щелочной батарейки на 23 часа работы, с литий-тионил батарейкой можно работать 44 часа непрерывно. Максимальное входное напряжение 40В. Это на тот случай, если нет под рукой подходящей батарейки. Из  органов управления есть всего один энкодер, пока неудобства не испытывал. Вся информация отображается на OLED дисплее. В устройстве есть два входа: логический и для микрофона. Все входы разведены на стандартный аудио штекер 3,5мм. К логическому входу кроме камеры можно подключить фототранзистор или фотодиод. Есть три гальванически развязанных выхода. Выходы «1» и «2» совмещены и разведены на штекер 2,5мм, сюда можно подсоединить дистанционный кабель камеры. Выход «3» разведён на штекер 3,5мм и «горячий башмак», можно установить вспышку или радио - триггер непосредственно на «МВ». К выходу «3» можно подключать высоковольтные устройства до 300В. Из железа пока ничего больше не планируется. Есть пару идей в будущем добавить bluetooth и организовать сеть между приборами и управлять всем через смарт телефон.
 
 


О программе.

В первую очередь в этом релизе делался упор на совместную работу «МВ» и студийного света.
/*
 * @name        Time machine.
 * @version     1.0
 * @web         http://karu2003.blogspot.de/
 * @author      Andrew Buckin, Eugene Glushko.
 *
 *
 * "Time machine" 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.
 *
 * "Time machine" 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 "Time machine".  If not, see <http://www.gnu.org/licenses/>.
 * Questions?  Send mail to ka_ru2003@msn.com
 */

#include <Wire.h>
#include <math.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define NO_PORTD_PINCHANGES
#define NO_PORTB_PINCHANGES

#include <PinChangeInt.h>
#include <SPI.h>

#include <EEPROM.h>

#define OLED_RESET_PIN 10

struct delay_entry_t {
    uint16_t exposure;
    uint16_t delay;
    uint8_t  dirty;
};

enum ui_mode_e {
    BROWSING,
    EDITING
};

enum gen_mode_e {
    IDLE,
    DELAY,
    PULSE
};

struct status_t {
    ui_mode_e  ui_mode;
    gen_mode_e gen_mode;
    int8_t     index;
    int8_t     digit;
};

delay_entry_t entries[] = {
//    { 60,   1001, 0 },
//    { 80,   1002, 0 },
//    { 100,  1003, 0 },
//    { 125,  1004, 0 },
//    { 160,  1005, 0 },
//    { 200,  1006, 0 },
    { 250,  1007, 0 },
//    { 320,  1008, 0 },
//    { 400,  1009, 0 },
    { 500,  1010, 0 },
//    { 640,  1011, 0 },
//    { 800,  1012, 0 },
    { 1000, 1013, 0 },
//    { 1250, 1014, 0 },
//    { 1600, 1015, 0 },
    { 2000, 1016, 0 },
//    { 2500, 1017, 0 },
//    { 3200, 1018, 0 },
    { 4000, 1019, 0 },
//    { 5000, 1020, 0 },
//    { 6400, 1021, 0 },
    { 8000, 1022, 0 }
};

#define SAVE_EVENT 1
#define SHDN_EVENT 2

#define DEBOUNCING_INTERVAL 20
#define SAVE_INTERVAL       2000
#define SHDN_INTERVAL       5000

#define DEFAULT_INVERSE_MASK 0

#define PULSE_DELAY 500

#define A_EN_PIN A3
#define B_EN_PIN A2
#define INT_PIN  A1
#define KILL_PIN A0

#define FLASH_IN 2

#define OUT1_PIN 4
#define OUT2_PIN 5
#define OUT3_PIN 8

#define VREF 6

#define TPL0501_CS1 7  // Vref
#define TPL0501_CS2 9  // Gain

#define AIN_PIN  A6

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

#define TEXT_SIZE 2

Adafruit_SSD1306 display(OLED_RESET_PIN);

volatile status_t status = { BROWSING, IDLE, 0, 0 };

char screen_buf[9] = { 0 };
uint8_t inverse_mask = DEFAULT_INVERSE_MASK;

static volatile uint8_t button_clicks = 0;
static volatile uint8_t events = 0;

static volatile uint8_t need_reinit_display = 0;
static volatile uint8_t need_redraw = 0;

// interrupt service routine vars
volatile boolean ABc = false;
volatile boolean BBc = false;
volatile boolean AAc = false;
volatile boolean BAc = false;
volatile int8_t clicks = 0;      // Counter to indicate cumulative clicks in either direction
volatile int8_t direction = NULL;   // indicator
volatile int8_t enc = NULL;   // indicator

void setup()
{
    display.begin(SSD1306_SWITCHCAPVCC);
    display.setTextSize(TEXT_SIZE);

    displayPrint("READING", DEFAULT_INVERSE_MASK);
    readDelays();
    delay(500);

    renderScreen();
    displayPrint(screen_buf, inverse_mask);

    pinMode(INT_PIN, INPUT);
    pinMode(A_EN_PIN, INPUT);
    pinMode(B_EN_PIN, INPUT);
    pinMode(FLASH_IN, INPUT);

    digitalWrite(FLASH_IN, HIGH);

    pinMode(OUT1_PIN, OUTPUT);
    pinMode(OUT2_PIN, OUTPUT);
    pinMode(OUT3_PIN, OUTPUT);

    TCCR1A = 0x00;
    TCCR1C = 0x00;
    TIMSK1 &= ~_BV(TOIE1);

    attachInterrupt(0, int0_isr, FALLING);
    
    PCintPort::attachInterrupt(A_EN_PIN, &doEncoderA, CHANGE);
    PCintPort::attachInterrupt(B_EN_PIN, &doEncoderB, CHANGE);
//----------SPI init    
    pinMode (TPL0501_CS1, OUTPUT);
    pinMode (TPL0501_CS2, OUTPUT);
    digitalWrite(TPL0501_CS1,HIGH);
    digitalWrite(TPL0501_CS2,HIGH);
    SPI.begin();
    Serial.begin(9600);
}

void loop()
{
    int8_t clicks = 0;

    for (;;) {
        handle_button();
        handle_events();
        
        if (direction != NULL){
         enc = !NULL;
          if (direction > 0) clicks=+1;
          else clicks=-1;
         direction = NULL;
        }
        if (enc != NULL) {
            
            need_redraw = 1;
            if (status.ui_mode == BROWSING) {
                status.index =
                        wrap(status.index + clicks, ARRAY_SIZE(entries));
            } else {
                edit_digit(clicks);
            }
            enc = NULL;
        }

        if (get_button_clicks()) {
            need_redraw = 1;
            if (status.ui_mode == BROWSING) {
                status.ui_mode = EDITING;
                status.digit = 0;
            } else {
                status.digit++;
                if (status.digit == 4) {
                    status.ui_mode = BROWSING;
                }
            }
        }

        if (need_redraw || need_reinit_display) {
            need_redraw = 0;
            renderScreen();
            displayPrint(screen_buf, inverse_mask);
        }
    }
}

void edit_digit(int8_t delta)
{
    uint8_t digits[4];
    uint16_t number = entries[status.index].delay;
    switch (status.digit) {
    case 0:
      number = number + (delta *1000);
      if (number >= 10000){number = number - (delta *1000);}
      break;
    case 1:
      number = number + (delta *100);
      if (number >= 10000) {number = number - (delta *100);}
      break;
    case 2:
      number = number + (delta *10);
      if (number >= 10000) {number = number - (delta *10);}
      break;
    case 3:
      number = number + (delta *1);
      if (number >= 10000) {number = number - (delta *1);}
      break;      
    }
    Serial.println(number, DEC);
    entries[status.index].delay = number;
    entries[status.index].dirty = 1;
}

uint8_t handle_events()
{
    if (events & _BV(SAVE_EVENT)) {
        displayPrint("SAVING", 0);
        writeDelays();
        delay(500);
        events &= ~(_BV(SAVE_EVENT));
        status.ui_mode = BROWSING;
        need_redraw = 1;
        return 1;
    } else if (events & _BV(SHDN_EVENT)) {
        displayPrint("SHUTDOWN", 0);
        delay(500);
        digitalWrite(KILL_PIN, 0);

        for (;;)
            ;

        return 0;
    }
}

int8_t wrap(int8_t value, int8_t limit)
{
    return (value >= 0) ? value % limit
                        : limit - (-value % limit);
}

void readDelays()
{
    for (uint16_t i = 0; i < ARRAY_SIZE(entries); i++) {
        uint8_t lo = EEPROM.read(i * 2);
        uint8_t hi = EEPROM.read(i * 2 + 1);

        uint16_t dly = (hi << 8) + lo;

        entries[i].delay = dly >= 10000 ? 9999 : dly;
    }
}

void writeDelays()
{
    for (uint16_t i = 0; i < ARRAY_SIZE(entries); i++) {
        if (entries[i].dirty) {
            EEPROM.write(i * 2, entries[i].delay & 0xFF);
            EEPROM.write(i * 2 + 1, entries[i].delay >> 8);
        }
    }
}

void renderScreen()
{
    if (status.ui_mode == BROWSING) {
        inverse_mask = 0x00;
    } else {
        inverse_mask = _BV(4 + status.digit);
    }

    strcpy(screen_buf, format_exposure(entries[status.index].exposure));
    if (status.ui_mode == BROWSING){
    strcat(screen_buf, ">");
    }
    if (status.ui_mode == EDITING){
    strcat(screen_buf, "<");
    }
    
    strcat(screen_buf, i2a(entries[status.index].delay, DEC, 4));
}

void displayPrint(char *s, uint8_t inverse_mask)
{
    uint16_t len = strlen(s);

    if (len > 8) {
        len = 8;
    }

    if (need_reinit_display) {
        display.begin(SSD1306_SWITCHCAPVCC);
        display.setTextSize(TEXT_SIZE);
        need_reinit_display = 0;
    }

    display.setCursor(0, 1);
    display.clearDisplay();
    display.fillRect(0, 0, display.width(), display.height(), BLACK);

    for (uint8_t i = 0; i < len; i++) {
        if (((inverse_mask >> i) & 0x01) == 1) {
             display.setTextColor(BLACK, WHITE);
        } else {
            display.setTextColor(WHITE, BLACK);
        }
        display.print(s[i]);
    }

    display.setTextColor(WHITE, BLACK);
    display.display();
}

char *format_exposure(uint16_t exposure)
{

#define MAX_EXPOSURE_STRING_LENGTH 4
#define THOUSANDS_POS              0
#define HUNDREDS_POS               2

    static char exposure_str[MAX_EXPOSURE_STRING_LENGTH] = "0K0";

#define DEC 10

    if (exposure < 1000) {
        return i2a(exposure, DEC, 3);
    }

#define ERROR_STR "???"

    if (exposure >= 10000) {
        return ERROR_STR;
    }

    uint8_t thousands = (exposure / 1000);
    uint8_t hundreds = (exposure - thousands * 1000) / 100;

    exposure_str[THOUSANDS_POS] = thousands + '0';
    exposure_str[HUNDREDS_POS] = hundreds + '0';

    return exposure_str;
}

char *i2a(int32_t i, uint8_t base, int8_t positions)
{
    enum { PLUS, MINUS };

#define I2A_BUF_LEN 12

    static char buf[I2A_BUF_LEN] = { 0 };
    uint8_t j = I2A_BUF_LEN - 2;
    uint8_t sign = PLUS;

    int8_t positions_left = 0;

    if (i == 0) {
        if (positions == 0) {
            buf[j--] = '0';
        } else {
            while (j && positions--) {
                buf[j--] = '0';
            }
        }
        return &buf[j + 1];
    } else if (i < 0) {
        i = -i;
        sign = MINUS;
    }

    base = base < BIN ? BIN : base;
    base = base > HEX ? HEX : base;

    for(; i && j; j--, i /= base) {
        buf[j] = "0123456789ABCDEF"[i % base];
    }

    if (sign == MINUS) {
        buf[j--] = '-';
    } else {
        positions_left = positions - (I2A_BUF_LEN - (int8_t) j) + 2;
        while (j && positions_left > 0) {
            buf[j--] = '0';
            positions_left--;
        }
    }

    return &buf[j + 1];
}

uint8_t get_button_clicks()
{
    uint8_t i = button_clicks;

    button_clicks = 0;

    return i;
}

void handle_button()
{
    static uint32_t timestamp = 0;
    static uint8_t debouncing = 0;
    static uint8_t holding = 0;
    static uint8_t saved = 0;

    uint8_t button_state;

    button_state = digitalRead(INT_PIN);

    if (debouncing) {
        if (millis() - timestamp > DEBOUNCING_INTERVAL) {
            debouncing = 0;
            if (button_state == LOW) {
                holding = 1;
                button_clicks++;
            } else {
                timestamp = 0;
            }
        }
        return;
    }

    if (button_state == HIGH) {
        timestamp = 0;
        holding = 0;
        saved = 0;
        return;
    }

    if (!holding) {
        timestamp = millis();
        debouncing = 1;
        return;
    }

    if (millis() - timestamp > SAVE_INTERVAL && !saved) {
        events |= _BV(SAVE_EVENT);
        saved = 1;
        return;
    }

    if (millis() - timestamp > SHDN_INTERVAL) {
        events |= _BV(SHDN_EVENT);
        return;
    }
}

#define TCNT1_PULSE (65536 - (50 << 1))
ISR(TIMER1_OVF_vect)
{
    noInterrupts();
    TIMSK1 &= ~(_BV(TOIE1));
    
    if (status.gen_mode == DELAY) {
        status.gen_mode = PULSE;    

        TCNT1H = (TCNT1_PULSE >> 8);
        TCNT1L = (TCNT1_PULSE & 0xFF);

        digitalWrite(OUT1_PIN, HIGH);
        digitalWrite(OUT2_PIN, HIGH);
        digitalWrite(OUT3_PIN, HIGH);
        
        TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
        //interrupts();
    } //else {
    if (status.gen_mode == PULSE) {
        status.gen_mode = IDLE; 
        digitalWrite(OUT1_PIN, LOW);
        digitalWrite(OUT2_PIN, LOW);
        digitalWrite(OUT3_PIN, LOW);
        
        TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12));
        
        need_reinit_display = 1;
        //interrupts();
    }
    interrupts();
}

void int0_isr()
{
    noInterrupts();
    if (status.gen_mode == IDLE) {
        status.gen_mode = DELAY;

        uint16_t counter = entries[status.index].delay << 1;
        if (counter == 0) counter = 1;
        counter = 65536-counter;
        TCNT1H = (counter >> 8);
        TCNT1L = (counter & 0xFF);

        // 16MHz / 8 -> 0.5 us / tick
        TCCR1B |= (1 << CS11);    // 8 prescaler 
        TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
        
    }
    interrupts();
}

int Pot_value_SET(int Chip, int value) {
  // take the SS pin low to select the chip:
  digitalWrite(Chip,LOW);
  //  send in the value via SPI:
  SPI.transfer(value);
  // take the SS pin high to de-select the chip:
  digitalWrite(Chip,HIGH);
}

// Interrupt on A changing state
void doEncoderA(){
  AAc = digitalRead(A_EN_PIN);
  BAc = digitalRead(B_EN_PIN);
  if (!ABc && AAc && !BBc && !BAc){direction=1 ;}
  if (ABc && !AAc && BBc && BAc){direction=1;}
  if (!ABc && AAc && BBc && BAc){direction=-1;}
  if (ABc && !AAc && !BBc && !BAc){direction=-1;}
}

// Interrupt on B changing state
void doEncoderB(){
  ABc = digitalRead(A_EN_PIN);
  BBc = digitalRead(B_EN_PIN);
}


В одной из своих ранних статей я описывал  возможность работы студийного света на выдержках до 1/8000. Этот релиз заточен под скоростную съёмку в студии.  Всё управление осуществляется энкодером и кнопкой.



Кнопкой меняются режимы работы, а вращая энкодер можно изменить значения задержек.

Навигация.

Коротким нажатием прибор включается. После включения, на дисплее появиться сообщение «READING». Инициализируется прибор, читаются данные из постоянной памяти в оперативную, и прибор переходит в рабочей режим - BROWSING. Дисплей разбит на три области, первые три цифры показывают выдержку, символ «>» указывает что прибор в режиме «BROWSING», символ «<» указывает что прибор находиться в режиме «EDITING». Оставшиеся четыре знака показывают значение задержи в микросекундах, минимальная задержка 1мкС, максимальная задержка 9999мкС. В режиме «BROWSING» вращая энкодер можно выбрать выдержки. Я остановился на целых значения выдержек 1/250,1/500,1/1000,1/2000,1/4000,1/8000. Для любителей дробных величин «EV» можно убрать комментарии в программе. На дисплее выдержки показываются как 250,500,1К0,2К0,4К0,8К0 соответственно. :) Коротким нажатием на кнопку прибор переходит в режим EDITING. Редактируемый разряд показывается с инверсией. Вращая энкодер изменяем значения разряда +/-1000, +/-100, +/-10, +/-1. Перейти к следующему разряду можно коротким нажатием на кнопку. После редактирования последнего разряда прибор переходит в режим BROWSING. Всю последовательность я постарался показать в схеме. :)



Удерживая кнопку нажатой 2 секунды можно сохранить значения задержек в постоянную память. При включении не надо  настраивать «МВ» по новой. Если удерживать кнопку нажатой более 5 секунд, «МВ» выключиться. Это базовый набор команд. С дальнейшим развитием проекта функций будет больше.
   
Работа с «MB» на коротких выдержках.

В первую очередь хочу отметить, что работать можно с любым студийным светом. Сейчас выпускается свет по технологии с IGBT транзисторами, и без IGBT. Студийный свет с IGBT транзисторами более новая технология. Основное отличие между старой и новой технологией -  это длительность импульса и количество света при этом. У блоков на IGBT самая большая длительность импульса на максимальной мощности. У старых приборов все наоборот -  на максимальной мощности самая маленькая длительность. В добавок ко всему этому для генераторов существуют быстрые и медленные головы. Чтобы не запутывать вас больше, и не грузить вас электроникой, напишу коротко — для лучших результатов нужен длинный (широкий) импульс. Так сложилось что у меня самый широкий импульс с кольцевой вспышкой на максимальной мощности, и все особенности настройки я опишу для этого света. Можно порадоваться за владельцев старых систем, у них самый длинный импульс. :) Они могут снимать на меньшей мощности и на самых коротких выдержках.
 
Всё по порядку.

Нужна системная вспышка. В моём случае это SB-900. Вспышку нужно установить на камеру, переключить в режим M + FP на мощности 1/128. Подключить логический вход «Машины Времени» к PC разъёму камеры. Выход «3» подключить к моно-блоку.


На моно-блоке или генераторе нужно выключить светоловушку. Теперь нужно настроить задержки в «МВ» и повторить настройку для каждой выдержки. Выдержку на камере и «МВ» нужно синхронизовать самому. После настройки не забудьте сохранить значения в постоянную память. Для подбора задержек нужно сделать от 5 до 10 кадров. Я фотографирую белую стену.
Для примера: На камере выдержка 1/500. На «МВ» нужно установить 500, перейти в режим EDITING, установить 9000 и сделать пробный кард. Полученный кард нужно оценивать по наличию градиенты. Изменяя задержку в минус, нужно добиться, чтобы градиенты не было или она равномерно расходилась от центра кадра вверх и вниз. Для моего моно блока самая оптимальная задержка для выдержки 1/500 получилась 2000мкС. Такую процедуру нужно повторить для каждой выдержки.






После настройки у меня получились такие цифры.
1/500 — 2000uS.
1/1000 – 1500uS.
1/2000 – 1100uS.
1/8000 – 600uS.

С уменьшением выдержки нужно добавлять мощность на блоке. В моём генераторе на выдержке 1/8000 мне пришлось добавить 2 стопа относительно к 1/500, чтобы получить кадр с контролируемой градиентой. Увы электроника не всемогуща. Существуют блоки, с которыми нормально работать можно только до выдержки 1/2000.

Пару сравнительных примеров с «МВ» и без.

Это снимок работающего винчестера, скорость вращения 5400 оборотов в минуту. Виден смаз. 

С выдержкой 1/8000 всё замёрзло.


У меня есть старый серверный винчестер с 10000 rmp. Я сделал пару кадров и с ним. Тут видно, что камера уже не успевает. Смаз есть даже на 1/8000.




Пару кадров с водой.




В этих снимках я сделал маленькую ошибку установил фокус на душ, а надо было на воду. Но даже так хорошо видно, как, на выдержке 1/8000, вода разлетелась на отдельные капли, и кадр получился более эффектным и динамичным.

Удачных вам кадров.

© Andrew Buckin.

PS: Большое спасибо Жене в подготовке первого релиза. Все фотографии в большом разрешении есть у меня на Flickr.

TODO: 
- добавить режим сохранения энергии ( выключить дисплей) 
- После 10 минут неиспользования самовыключение. 
- Добавить работу с микрофоном. 
- Добавить независимые выдержки для каждого выхода. (работа по сценарию.) 
- Добавить измерение длительности импульса света.


Shutterstock Dreamstime
Fotostream http://www.flickr.com

3 Kommentare:

  1. Hi Andrew,

    ich beobachte Deine Entwicklungen (von DSLR-Forum aus) mit großem Interesse und finde viele Deiner Ideen einfach fantastisch! Welche Absichten verfolgst Du mit Deiner Time-Mashine? Ich meine - sie selbst nachzubauen, ist für mich z.B. eine zu große Nummer - willst Du die auf Bestellungen fertigen können, oder geht Deine Time-Mashne später gar in die serielle Produktion?

    Ich weiss nicht, ob Du Kickstarter kennst (http://www.kickstarter.com/), aber dort könntest Du für viele Deiner Projekte bestimmt mit leichtigkeit einen finanziellen Basis verschaffen. Das ist ein Plattform, wo die Entwickler eine Spenden-Unterstützung für ihre neue Entwicklungen bekommen könnnen (natürlich wenn öffentliches Interesse besteht, und natürlich wird das mit feedback-Belohnungen entsprechend honoriert). So fanden viele brilliante fototechnische Innovationen ihren weg in die Öffentlichkeit. Ich persönlich kenne z.B. Capture camera clip system (für mich deutlich besseres System als etwa Spyder) - das fing auch im Kickstarter an und heute ist Capture weltweit zu kaufen, bei uns in Deutschland z.B. über Enjoyyourcamera.

    Ich wünsche Dir aber auf jeden Fall viel Erfolg und weitere Ideen. Und Deine Einstellung zu Murphys Gesetzen ist mehr als gesund :)

    VG
    VicTor

    P.S. Sorry für mein Deutsch - alles über Translit.ru auf Russisch zu schreiben, war ich einfach zu faul :)

    AntwortenLöschen
    Antworten
    1. Danke für dein Feedback.

      Zu erst du kannst Deutsch schreiben. Mein Deutsch besser als Englisch.
      Deutsch verstehe ich genau so gut wie Russisch. :)
      Mit der „Time-Mashne“ Absicht später Taschengeld zu verdienen. :)
      Nachbauen kein Problem. Ich kann auch fertigen. Jetzt warte ich auf erste 4 Geräten.
      Zwei brauche ich für Entwicklung, ein für Software Entwickler.
      Mit Spenden ist sehr verlocken. Werde nachdenken.

      Danke.
      Andrew.

      Löschen
  2. Harrah's Casino and Resort - Mapyro
    Harrah's Casino and Resort. 공주 출장안마 3355 South 충청북도 출장마사지 Lake Tahoe Blvd. (corner 광주 출장샵 of Stateline Highway and Stateline Road). 888.478.7755. Call Now 청주 출장안마 · More Info. Hours, 인천광역 출장샵 Attire, Wi-Fi

    AntwortenLöschen